blob: 4838ed864a38d48b0d4335d0a9e457cdb43baa56 [file] [log] [blame]
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/renderer/rasterizer/lib/external_rasterizer.h"
#include <algorithm>
#include <cmath>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/lazy_instance.h"
#include "base/threading/thread_checker.h"
#include "cobalt/math/clamp.h"
#include "cobalt/render_tree/image.h"
#include "cobalt/renderer/backend/egl/graphics_context.h"
#include "cobalt/renderer/backend/egl/render_target.h"
#include "cobalt/renderer/rasterizer/common/find_node.h"
#include "cobalt/renderer/rasterizer/egl/hardware_rasterizer.h"
#include "cobalt/renderer/rasterizer/lib/exported/graphics.h"
#include "cobalt/renderer/rasterizer/lib/exported/video.h"
#include "cobalt/renderer/rasterizer/skia/hardware_image.h"
#include "cobalt/renderer/rasterizer/skia/hardware_mesh.h"
#include "cobalt/renderer/rasterizer/skia/hardware_rasterizer.h"
#include "starboard/shared/gles/gl_call.h"
#include "third_party/glm/glm/gtc/matrix_transform.hpp"
#include "third_party/glm/glm/gtc/type_ptr.hpp"
COMPILE_ASSERT(
cobalt::render_tree::kMono == kCbLibVideoStereoModeMono &&
cobalt::render_tree::kLeftRight ==
kCbLibVideoStereoModeStereoLeftRight &&
cobalt::render_tree::kTopBottom ==
kCbLibVideoStereoModeStereoTopBottom &&
cobalt::render_tree::kLeftRightUnadjustedTextureCoords ==
kCbLibVideoStereoModeStereoLeftRightUnadjustedCoordinates,
lib_video_and_map_to_mesh_stereo_mode_constants_mismatch);
using cobalt::renderer::rasterizer::lib::ExternalRasterizer;
namespace {
static const float kMaxRenderTargetSize = 15360.0f;
// The minimum amount of change required in the desired texture size to generate
// a new offscreen render target for the quad texture.
static const float kMinTextureSizeEpsilon = 20.0f;
// Matches the signatures of the callback setter functions in exported/video.h
// and exported/graphics.h.
template <typename Ret, typename... Args>
using CallbackSetter = void(void*, Ret (*)(void*, Args...));
template <typename T, T* t, const char* ErrorMessage>
struct CallbackUpdate;
// Defines useful base:: wrappers for callback setter functions.
template <typename Ret, typename... Args, CallbackSetter<Ret, Args...>* Setter,
const char* ErrorMessage>
struct CallbackUpdate<CallbackSetter<Ret, Args...>, Setter, ErrorMessage> {
// Equivalent to the callback types defined in exported/video.h and
// exported/graphics.h but with the context bound.
using Callback = base::Callback<Ret(Args...)>;
static Ret DefaultImplementation(Args...) { LOG(WARNING) << ErrorMessage; }
struct LazyTraits {
static const bool kRegisterOnExit = true;
static const bool kAllowedToAccessOnNonjoinableThread = false;
static Callback* New(void* instance) {
return new (instance) Callback(base::Bind(DefaultImplementation));
}
static void Delete(Callback* instance) {
return base::DefaultLazyInstanceTraits<Callback>::Delete(instance);
}
};
// This provides a default warning function for the callbacks and allows to
// set them even before the external rasterizer is created.
using LazyCallback = base::LazyInstance<Callback, LazyTraits>;
};
// Creates an instance for the above template for a given callback setter
// function, with an error message.
// We must use 'extern' in here as otherwise we get compiler error C2970 when
// using MSVC for compilation.
#define INSTANCE_CALLBACK_UPDATE(instance_name, callback_setter) \
extern const char kWarningMessageDidNotSet##instance_name[] = \
#callback_setter \
"was never called to set a callback, yet Cobalt is " \
"attempting to call it."; \
using instance_name = \
CallbackUpdate<decltype(callback_setter), &callback_setter, \
kWarningMessageDidNotSet##instance_name>;
INSTANCE_CALLBACK_UPDATE(UpdateMeshes, CbLibVideoSetOnUpdateMeshes);
INSTANCE_CALLBACK_UPDATE(UpdateStereoMode, CbLibVideoSetOnUpdateStereoMode);
INSTANCE_CALLBACK_UPDATE(UpdateRgbTextureId, CbLibVideoSetOnUpdateRgbTextureId);
INSTANCE_CALLBACK_UPDATE(UpdateProjectionType,
CbLibVideoSetOnUpdateProjectionType);
INSTANCE_CALLBACK_UPDATE(GraphicsContextCreated,
CbLibGraphicsSetContextCreatedCallback);
INSTANCE_CALLBACK_UPDATE(BeginRenderFrame,
CbLibGraphicsSetBeginRenderFrameCallback);
INSTANCE_CALLBACK_UPDATE(EndRenderFrame,
CbLibGraphicsSetEndRenderFrameCallback);
#undef INSTANCE_CALLBACK_UPDATE
UpdateMeshes::LazyCallback g_update_meshes_callback = LAZY_INSTANCE_INITIALIZER;
UpdateStereoMode::LazyCallback g_update_stereo_mode_callback =
LAZY_INSTANCE_INITIALIZER;
UpdateRgbTextureId::LazyCallback g_update_rgb_texture_id_callback =
LAZY_INSTANCE_INITIALIZER;
UpdateProjectionType::LazyCallback g_update_projection_type_callback =
LAZY_INSTANCE_INITIALIZER;
GraphicsContextCreated::LazyCallback g_graphics_context_created_callback =
LAZY_INSTANCE_INITIALIZER;
BeginRenderFrame::LazyCallback g_begin_render_frame_callback =
LAZY_INSTANCE_INITIALIZER;
EndRenderFrame::LazyCallback g_end_render_frame_callback =
LAZY_INSTANCE_INITIALIZER;
bool ApproxEqual(const cobalt::math::Size& a, const cobalt::math::Size& b,
float epsilon) {
return std::abs(a.width() - b.width()) < epsilon &&
std::abs(a.height() - b.height()) < epsilon;
}
cobalt::math::Size CobaltSizeFromCbLibSize(CbLibSize size) {
return cobalt::math::Size(size.width, size.height);
}
ExternalRasterizer::Impl* g_external_rasterizer_impl = nullptr;
} // namespace
namespace cobalt {
namespace renderer {
namespace rasterizer {
namespace lib {
class ExternalRasterizer::Impl {
public:
Impl(backend::GraphicsContext* graphics_context, int skia_atlas_width,
int skia_atlas_height, int skia_cache_size_in_bytes,
int scratch_surface_cache_size_in_bytes, int surface_cache_size_in_bytes,
bool purge_skia_font_caches_on_destruction);
~Impl();
void Submit(const scoped_refptr<render_tree::Node>& render_tree,
const scoped_refptr<backend::RenderTarget>& render_target);
render_tree::ResourceProvider* GetResourceProvider();
void MakeCurrent() { hardware_rasterizer_.MakeCurrent(); }
intptr_t GetMainTextureHandle();
// Sets the target size in pixels to use for the main render target buffer.
void SetTargetMainTextureSize(const cobalt::math::Size& target_render_size) {
target_main_render_target_size_ = target_render_size;
}
private:
void RenderOffscreenVideo(render_tree::FilterNode* map_to_mesh_filter_node);
scoped_refptr<render_tree::MatrixTransformNode> UpdateTextureSizeAndWrapNode(
const cobalt::math::Size& native_render_target_size,
const scoped_refptr<render_tree::Node>& render_tree);
base::ThreadChecker thread_checker_;
backend::GraphicsContextEGL* graphics_context_;
#if defined(COBALT_FORCE_DIRECT_GLES_RASTERIZER)
egl::HardwareRasterizer hardware_rasterizer_;
#else
skia::HardwareRasterizer hardware_rasterizer_;
#endif
Rasterizer::Options options_;
// The main offscreen render target to use when rendering UI or rectangular
// video.
scoped_refptr<backend::RenderTarget> main_offscreen_render_target_;
scoped_ptr<backend::TextureEGL> main_texture_;
// TODO: do not actually rasterize offscreen video, but just submit it to the
// host directly.
scoped_refptr<backend::RenderTarget> video_offscreen_render_target_;
scoped_ptr<backend::TextureEGL> video_texture_;
CbLibVideoProjectionType video_projection_type_;
scoped_refptr<skia::HardwareMesh> left_eye_video_mesh_;
scoped_refptr<skia::HardwareMesh> right_eye_video_mesh_;
render_tree::StereoMode video_stereo_mode_;
GLuint video_texture_rgb_;
// The 'target'/'ideal' size to use for the main RenderTarget. The actual size
// of the buffer for the main RenderTarget should aim to be within some small
// delta of this whenever a new RenderTree is rendered.
cobalt::math::Size target_main_render_target_size_;
};
ExternalRasterizer::Impl::Impl(backend::GraphicsContext* graphics_context,
int skia_atlas_width, int skia_atlas_height,
int skia_cache_size_in_bytes,
int scratch_surface_cache_size_in_bytes,
int rasterizer_gpu_cache_size_in_bytes,
bool purge_skia_font_caches_on_destruction)
: graphics_context_(
base::polymorphic_downcast<backend::GraphicsContextEGL*>(
graphics_context)),
hardware_rasterizer_(graphics_context, skia_atlas_width,
skia_atlas_height, skia_cache_size_in_bytes,
scratch_surface_cache_size_in_bytes,
rasterizer_gpu_cache_size_in_bytes,
purge_skia_font_caches_on_destruction),
video_projection_type_(kCbLibVideoProjectionTypeNone),
video_stereo_mode_(render_tree::StereoMode::kMono),
video_texture_rgb_(0),
target_main_render_target_size_(1, 1) {
CHECK(!g_external_rasterizer_impl);
g_external_rasterizer_impl = this;
options_.flags = Rasterizer::kSubmitFlags_Clear;
graphics_context_->MakeCurrent();
main_offscreen_render_target_ =
graphics_context_->CreateOffscreenRenderTarget(
target_main_render_target_size_);
main_texture_.reset(new backend::TextureEGL(
graphics_context_,
make_scoped_refptr(base::polymorphic_downcast<backend::RenderTargetEGL*>(
main_offscreen_render_target_.get()))));
g_graphics_context_created_callback.Get().Run();
}
ExternalRasterizer::Impl::~Impl() {
graphics_context_->MakeCurrent();
g_external_rasterizer_impl = nullptr;
}
void ExternalRasterizer::Impl::Submit(
const scoped_refptr<render_tree::Node>& render_tree,
const scoped_refptr<backend::RenderTarget>& render_target) {
backend::RenderTargetEGL* render_target_egl =
base::polymorphic_downcast<backend::RenderTargetEGL*>(
render_target.get());
// When the provided RenderTarget is not a window RenderTarget, then this
// implies the rasterized RenderTree should not be shown directly to the user
// and thus should not be rasterized into a texture and sent through to the
// client implementing CbLibRenderFrame.
if (!render_target_egl->IsWindowRenderTarget()) {
hardware_rasterizer_.Submit(render_tree, render_target, options_);
return;
}
graphics_context_->MakeCurrentWithSurface(render_target_egl);
// Attempt to find map to mesh filter node, then render video subtree
// offscreen.
auto map_to_mesh_search = common::FindNode<render_tree::FilterNode>(
render_tree, base::Bind(common::HasMapToMesh),
base::Bind(common::ReplaceWithEmptyCompositionNode));
if (map_to_mesh_search.found_node != NULL) {
base::optional<render_tree::MapToMeshFilter> filter =
map_to_mesh_search.found_node->data().map_to_mesh_filter;
CbLibVideoProjectionType new_projection_type;
if (!filter->left_eye_mesh()) {
// Video is rectangular. Mesh is provided externally (by host).
new_projection_type = kCbLibVideoProjectionTypeRectangular;
} else {
new_projection_type = kCbLibVideoProjectionTypeMesh;
}
if (video_projection_type_ != new_projection_type) {
video_projection_type_ = new_projection_type;
g_update_projection_type_callback.Get().Run(video_projection_type_);
}
if (filter->stereo_mode() != video_stereo_mode_) {
video_stereo_mode_ = filter->stereo_mode();
g_update_stereo_mode_callback.Get().Run(
static_cast<CbLibVideoStereoMode>(video_stereo_mode_));
}
if (video_projection_type_ == kCbLibVideoProjectionTypeMesh) {
// Use resolution to lookup custom mesh map.
const scoped_refptr<render_tree::Node>& video_render_tree =
map_to_mesh_search.found_node->data().source;
math::SizeF resolutionf = video_render_tree->GetBounds().size();
int width = static_cast<int>(resolutionf.width());
int height = static_cast<int>(resolutionf.height());
math::Size resolution(width, height);
scoped_refptr<skia::HardwareMesh> left_eye_video_mesh(
base::polymorphic_downcast<skia::HardwareMesh*>(
filter->left_eye_mesh(resolution).get()));
scoped_refptr<skia::HardwareMesh> right_eye_video_mesh(
base::polymorphic_downcast<skia::HardwareMesh*>(
filter->right_eye_mesh(resolution).get()));
DCHECK(left_eye_video_mesh);
if (left_eye_video_mesh_.get() != left_eye_video_mesh.get() ||
right_eye_video_mesh_.get() != right_eye_video_mesh.get()) {
left_eye_video_mesh_ = left_eye_video_mesh;
right_eye_video_mesh_ = right_eye_video_mesh;
CbLibVideoMesh left_mesh;
left_mesh.vertex_count =
static_cast<int>(left_eye_video_mesh_->GetVertexCount());
left_mesh.draw_mode = static_cast<CbLibVideoMeshDrawMode>(
left_eye_video_mesh_->GetDrawMode());
left_mesh.vertices = left_eye_video_mesh_->GetVertices();
if (right_eye_video_mesh_) {
CbLibVideoMesh right_mesh;
right_mesh.vertex_count =
static_cast<int>(right_eye_video_mesh_->GetVertexCount());
right_mesh.draw_mode = static_cast<CbLibVideoMeshDrawMode>(
right_eye_video_mesh_->GetDrawMode());
right_mesh.vertices = right_eye_video_mesh_->GetVertices();
g_update_meshes_callback.Get().Run(left_mesh, right_mesh);
} else {
g_update_meshes_callback.Get().Run(left_mesh, left_mesh);
}
}
}
// Render video to external texture(s) and pass those to the host.
RenderOffscreenVideo(map_to_mesh_search.found_node);
} else {
if (video_projection_type_ != kCbLibVideoProjectionTypeNone) {
video_projection_type_ = kCbLibVideoProjectionTypeNone;
g_update_projection_type_callback.Get().Run(video_projection_type_);
}
}
const scoped_refptr<render_tree::MatrixTransformNode> scaled_main_node =
UpdateTextureSizeAndWrapNode(render_target->GetSize(),
map_to_mesh_search.replaced_tree);
hardware_rasterizer_.Submit(scaled_main_node, main_offscreen_render_target_,
options_);
// TODO: Allow clients to specify arbitrary subtrees to render into
// different textures?
g_begin_render_frame_callback.Get().Run();
graphics_context_->SwapBuffers(render_target_egl);
g_end_render_frame_callback.Get().Run();
}
// TODO: Share this logic with the ComponentRenderer.
scoped_refptr<render_tree::MatrixTransformNode>
ExternalRasterizer::Impl::UpdateTextureSizeAndWrapNode(
const cobalt::math::Size& native_render_target_size,
const scoped_refptr<render_tree::Node>& render_tree) {
// Create a new offscreen render target if the exist one's size is far enough
// off from the target/ideal size.
if (!main_offscreen_render_target_ ||
!ApproxEqual(main_offscreen_render_target_->GetSize(),
target_main_render_target_size_, kMinTextureSizeEpsilon)) {
LOG(INFO) << "Creating a new offscreen render target of size "
<< target_main_render_target_size_;
main_offscreen_render_target_ =
graphics_context_->CreateOffscreenRenderTarget(
target_main_render_target_size_);
// Note: The TextureEGL this pointer references must first be destroyed by
// calling reset() before a new TextureEGL can be constructed.
main_texture_.reset();
main_texture_.reset(new backend::TextureEGL(
graphics_context_,
make_scoped_refptr(
base::polymorphic_downcast<backend::RenderTargetEGL*>(
main_offscreen_render_target_.get()))));
}
DCHECK(native_render_target_size.width());
DCHECK(native_render_target_size.height());
// We wrap the RenderTree in a MatrixTransformNode to scale the RenderTree so
// that its scale relative to our RenderTarget matches its original scale
// relative to native_render_target_size. This makes the texture cropped to
// fit our RenderTarget the same amount it would be for the original
// RenderTarget.
const float texture_x_scale =
static_cast<float>(main_offscreen_render_target_->GetSize().width()) /
native_render_target_size.width();
const float texture_y_scale =
static_cast<float>(main_offscreen_render_target_->GetSize().height()) /
native_render_target_size.height();
const glm::mat3 scale_mat(glm::scale(
glm::mat4(1.0f), glm::vec3(texture_x_scale, texture_y_scale, 1)));
const cobalt::math::Matrix3F root_transform_matrix =
cobalt::math::Matrix3F::FromArray(glm::value_ptr(scale_mat));
return scoped_refptr<render_tree::MatrixTransformNode>(
new render_tree::MatrixTransformNode(render_tree, root_transform_matrix));
}
render_tree::ResourceProvider* ExternalRasterizer::Impl::GetResourceProvider() {
return hardware_rasterizer_.GetResourceProvider();
}
intptr_t ExternalRasterizer::Impl::GetMainTextureHandle() {
return main_texture_->GetPlatformHandle();
}
void ExternalRasterizer::Impl::RenderOffscreenVideo(
render_tree::FilterNode* map_to_mesh_filter_node) {
DCHECK(map_to_mesh_filter_node);
if (!map_to_mesh_filter_node) {
return;
}
// Render the mesh-video into a texture to render into 3D space.
const scoped_refptr<render_tree::Node>& video_render_tree =
map_to_mesh_filter_node->data().source;
// Search the video_render_tree for the video frame ImageNode.
auto image_node =
common::FindNode<render_tree::ImageNode>(video_render_tree).found_node;
math::SizeF video_size_float = video_render_tree->GetBounds().size();
if (image_node.get() && image_node->data().source) {
video_size_float = image_node->data().source.get()->GetSize();
}
// Width and height of the video render target are based on the size of the
// video clamped to the valid range for creating an offscreen render target.
const float target_width = math::Clamp(std::floor(video_size_float.width()),
1.0f, kMaxRenderTargetSize);
const float target_height = math::Clamp(std::floor(video_size_float.height()),
1.0f, kMaxRenderTargetSize);
const math::Size video_size(target_width, target_height);
if (!video_offscreen_render_target_ ||
video_offscreen_render_target_->GetSize() != video_size) {
video_offscreen_render_target_ =
graphics_context_->CreateOffscreenRenderTarget(video_size);
DLOG(INFO) << "Created new video_offscreen_render_target_: "
<< video_offscreen_render_target_->GetSize();
// Note: The TextureEGL this pointer references must first be destroyed by
// calling reset() before a new TextureEGL can be constructed.
video_texture_.reset();
video_texture_.reset(new backend::TextureEGL(
graphics_context_,
make_scoped_refptr(
base::polymorphic_downcast<backend::RenderTargetEGL*>(
video_offscreen_render_target_.get()))));
}
if (image_node.get()) {
// Create a new ImageNode around the raw image data which will
// automatically scale it to the right size.
backend::RenderTargetEGL* video_offscreen_render_target_egl =
base::polymorphic_downcast<backend::RenderTargetEGL*>(
video_offscreen_render_target_.get());
const scoped_refptr<render_tree::ImageNode> correctly_scaled_image_node(
new render_tree::ImageNode(image_node->data().source));
// TODO: Instead of submitting the image for rendering and producing an
// RGB texture, try to cast to HardwareMultiPlaneImage to use the YUV
// textures already produced by decode-to-texture.
hardware_rasterizer_.Submit(correctly_scaled_image_node,
video_offscreen_render_target_, options_);
const intptr_t video_texture_handle = video_texture_->GetPlatformHandle();
if (video_texture_rgb_ != video_texture_handle) {
video_texture_rgb_ = video_texture_handle;
g_update_rgb_texture_id_callback.Get().Run(video_texture_handle);
}
}
}
ExternalRasterizer::ExternalRasterizer(
backend::GraphicsContext* graphics_context, int skia_atlas_width,
int skia_atlas_height, int skia_cache_size_in_bytes,
int scratch_surface_cache_size_in_bytes,
int rasterizer_gpu_cache_size_in_bytes,
bool purge_skia_font_caches_on_destruction)
: impl_(new Impl(
graphics_context, skia_atlas_width, skia_atlas_height,
skia_cache_size_in_bytes, scratch_surface_cache_size_in_bytes,
rasterizer_gpu_cache_size_in_bytes,
purge_skia_font_caches_on_destruction)) {
}
ExternalRasterizer::~ExternalRasterizer() {}
void ExternalRasterizer::Submit(
const scoped_refptr<render_tree::Node>& render_tree,
const scoped_refptr<backend::RenderTarget>& render_target,
const Options& options) {
impl_->Submit(render_tree, render_target);
}
render_tree::ResourceProvider* ExternalRasterizer::GetResourceProvider() {
return impl_->GetResourceProvider();
}
void ExternalRasterizer::MakeCurrent() {
return impl_->MakeCurrent();
}
} // namespace lib
} // namespace rasterizer
} // namespace renderer
} // namespace cobalt
void CbLibVideoSetOnUpdateMeshes(void* context,
CbLibVideoUpdateMeshesCallback callback) {
g_update_meshes_callback.Get() =
callback ? base::Bind(callback, context)
: base::Bind(&UpdateMeshes::DefaultImplementation);
}
void CbLibVideoSetOnUpdateStereoMode(
void* context, CbLibVideoUpdateStereoModeCallback callback) {
g_update_stereo_mode_callback.Get() =
callback ? base::Bind(callback, context)
: base::Bind(&UpdateStereoMode::DefaultImplementation);
}
void CbLibVideoSetOnUpdateRgbTextureId(
void* context, CbLibVideoUpdateRgbTextureIdCallback callback) {
g_update_rgb_texture_id_callback.Get() =
callback ? base::Bind(callback, context)
: base::Bind(&UpdateRgbTextureId::DefaultImplementation);
}
void CbLibVideoSetOnUpdateProjectionType(
void* context, CbLibVideoUpdateProjectionTypeCallback callback) {
g_update_projection_type_callback.Get() =
callback ? base::Bind(callback, context)
: base::Bind(&UpdateProjectionType::DefaultImplementation);
}
void CbLibGraphicsSetContextCreatedCallback(
void* context, CbLibGraphicsContextCreatedCallback callback) {
g_graphics_context_created_callback.Get() =
callback ? base::Bind(callback, context)
: base::Bind(&GraphicsContextCreated::DefaultImplementation);
}
void CbLibGraphicsSetBeginRenderFrameCallback(
void* context, CbLibGraphicsBeginRenderFrameCallback callback) {
g_begin_render_frame_callback.Get() =
callback ? base::Bind(callback, context)
: base::Bind(&BeginRenderFrame::DefaultImplementation);
}
void CbLibGraphicsSetEndRenderFrameCallback(
void* context, CbLibGraphicsEndRenderFrameCallback callback) {
g_end_render_frame_callback.Get() =
callback ? base::Bind(callback, context)
: base::Bind(&EndRenderFrame::DefaultImplementation);
}
intptr_t CbLibGrapicsGetMainTextureHandle() {
DCHECK(g_external_rasterizer_impl);
if (!g_external_rasterizer_impl) {
LOG(WARNING) << __FUNCTION__
<< "ExternalRasterizer not yet created; unable to progress.";
return 0;
}
return g_external_rasterizer_impl->GetMainTextureHandle();
}
void CbLibGraphicsSetTargetMainTextureSize(
const CbLibSize& target_render_size) {
DCHECK(g_external_rasterizer_impl);
if (!g_external_rasterizer_impl) {
LOG(WARNING) << __FUNCTION__
<< "ExternalRasterizer not yet created; unable to progress.";
return;
}
const cobalt::math::Size size = CobaltSizeFromCbLibSize(target_render_size);
g_external_rasterizer_impl->SetTargetMainTextureSize(size);
}