From e600fb93a5e5cb6bdbc496393f9d96dd9a664292 Mon Sep 17 00:00:00 2001 From: clayjohn Date: Wed, 12 Oct 2022 17:55:01 -0700 Subject: Add 2D lights to OpenGL3 canvas renderer This is an initial implementation using the same single-pass approach as the RenderingDevice. --- drivers/gles3/effects/copy_effects.cpp | 2 +- drivers/gles3/effects/copy_effects.h | 2 +- drivers/gles3/rasterizer_canvas_gles3.cpp | 302 ++++++++++++++++++++++--- drivers/gles3/rasterizer_canvas_gles3.h | 45 +++- drivers/gles3/shaders/canvas.glsl | 170 +++++++++++++- drivers/gles3/shaders/canvas_uniforms_inc.glsl | 21 ++ drivers/gles3/shaders/copy.glsl | 5 +- drivers/gles3/storage/texture_storage.cpp | 236 ++++++++++++++++++- drivers/gles3/storage/texture_storage.h | 51 +++++ drivers/gles3/storage/utilities.cpp | 1 + 10 files changed, 792 insertions(+), 43 deletions(-) (limited to 'drivers/gles3') diff --git a/drivers/gles3/effects/copy_effects.cpp b/drivers/gles3/effects/copy_effects.cpp index de0181f887..92c29a4264 100644 --- a/drivers/gles3/effects/copy_effects.cpp +++ b/drivers/gles3/effects/copy_effects.cpp @@ -114,7 +114,7 @@ CopyEffects::~CopyEffects() { copy.shader.version_free(copy.shader_version); } -void CopyEffects::copy_to_rect(const Rect2i &p_rect) { +void CopyEffects::copy_to_rect(const Rect2 &p_rect) { copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_COPY_SECTION); copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, p_rect.position.x, p_rect.position.y, p_rect.size.x, p_rect.size.y, copy.shader_version, CopyShaderGLES3::MODE_COPY_SECTION); glBindVertexArray(quad_array); diff --git a/drivers/gles3/effects/copy_effects.h b/drivers/gles3/effects/copy_effects.h index b863e76579..7f16b4e0f3 100644 --- a/drivers/gles3/effects/copy_effects.h +++ b/drivers/gles3/effects/copy_effects.h @@ -61,7 +61,7 @@ public: ~CopyEffects(); // These functions assume that a framebuffer and texture are bound already. They only manage the shader, uniforms, and vertex array. - void copy_to_rect(const Rect2i &p_rect); + void copy_to_rect(const Rect2 &p_rect); void copy_screen(); void bilinear_blur(GLuint p_source_texture, int p_mipmap_count, const Rect2i &p_region); void set_color(const Color &p_color, const Rect2i &p_region); diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index ab232e712c..8c4234fe3c 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -115,7 +115,7 @@ void RasterizerCanvasGLES3::_update_transform_to_mat4(const Transform3D &p_trans p_mat4[15] = 1; } -void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used) { +void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_light_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used) { GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton(); @@ -144,9 +144,173 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ } } - // TODO: Setup Directional Lights + //setup directional lights if exist - // TODO: Setup lights + uint32_t light_count = 0; + uint32_t directional_light_count = 0; + { + Light *l = p_directional_light_list; + uint32_t index = 0; + + while (l) { + if (index == data.max_lights_per_render) { + l->render_index_cache = -1; + l = l->next_ptr; + continue; + } + + CanvasLight *clight = canvas_light_owner.get_or_null(l->light_internal); + if (!clight) { //unused or invalid texture + l->render_index_cache = -1; + l = l->next_ptr; + ERR_CONTINUE(!clight); + } + + Vector2 canvas_light_dir = l->xform_cache.columns[1].normalized(); + + state.light_uniforms[index].position[0] = -canvas_light_dir.x; + state.light_uniforms[index].position[1] = -canvas_light_dir.y; + + //_update_transform_2d_to_mat2x4(clight->shadow.directional_xform, state.light_uniforms[index].shadow_matrix); + + state.light_uniforms[index].height = l->height; //0..1 here + + for (int i = 0; i < 4; i++) { + state.light_uniforms[index].shadow_color[i] = uint8_t(CLAMP(int32_t(l->shadow_color[i] * 255.0), 0, 255)); + state.light_uniforms[index].color[i] = l->color[i]; + } + + state.light_uniforms[index].color[3] = l->energy; //use alpha for energy, so base color can go separate + + /* + if (state.shadow_fb.is_valid()) { + state.light_uniforms[index].shadow_pixel_size = (1.0 / state.shadow_texture_size) * (1.0 + l->shadow_smooth); + state.light_uniforms[index].shadow_z_far_inv = 1.0 / clight->shadow.z_far; + state.light_uniforms[index].shadow_y_ofs = clight->shadow.y_offset; + } else { + state.light_uniforms[index].shadow_pixel_size = 1.0; + state.light_uniforms[index].shadow_z_far_inv = 1.0; + state.light_uniforms[index].shadow_y_ofs = 0; + } + */ + + state.light_uniforms[index].flags = l->blend_mode << LIGHT_FLAGS_BLEND_SHIFT; + state.light_uniforms[index].flags |= l->shadow_filter << LIGHT_FLAGS_FILTER_SHIFT; + /* + if (clight->shadow.enabled) { + state.light_uniforms[index].flags |= LIGHT_FLAGS_HAS_SHADOW; + } + */ + + l->render_index_cache = index; + + index++; + l = l->next_ptr; + } + + light_count = index; + directional_light_count = light_count; + state.using_directional_lights = directional_light_count > 0; + } + + //setup lights if exist + + { + Light *l = p_light_list; + uint32_t index = light_count; + + while (l) { + if (index == data.max_lights_per_render) { + l->render_index_cache = -1; + l = l->next_ptr; + continue; + } + + CanvasLight *clight = canvas_light_owner.get_or_null(l->light_internal); + if (!clight) { //unused or invalid texture + l->render_index_cache = -1; + l = l->next_ptr; + ERR_CONTINUE(!clight); + } + Transform2D to_light_xform = (p_canvas_transform * l->light_shader_xform).affine_inverse(); + + Vector2 canvas_light_pos = p_canvas_transform.xform(l->xform.get_origin()); //convert light position to canvas coordinates, as all computation is done in canvas coords to avoid precision loss + state.light_uniforms[index].position[0] = canvas_light_pos.x; + state.light_uniforms[index].position[1] = canvas_light_pos.y; + + _update_transform_2d_to_mat2x4(to_light_xform, state.light_uniforms[index].matrix); + _update_transform_2d_to_mat2x4(l->xform_cache.affine_inverse(), state.light_uniforms[index].shadow_matrix); + + state.light_uniforms[index].height = l->height * (p_canvas_transform.columns[0].length() + p_canvas_transform.columns[1].length()) * 0.5; //approximate height conversion to the canvas size, since all calculations are done in canvas coords to avoid precision loss + for (int i = 0; i < 4; i++) { + state.light_uniforms[index].shadow_color[i] = uint8_t(CLAMP(int32_t(l->shadow_color[i] * 255.0), 0, 255)); + state.light_uniforms[index].color[i] = l->color[i]; + } + + state.light_uniforms[index].color[3] = l->energy; //use alpha for energy, so base color can go separate + + /* + if (state.shadow_fb.is_valid()) { + state.light_uniforms[index].shadow_pixel_size = (1.0 / state.shadow_texture_size) * (1.0 + l->shadow_smooth); + state.light_uniforms[index].shadow_z_far_inv = 1.0 / clight->shadow.z_far; + state.light_uniforms[index].shadow_y_ofs = clight->shadow.y_offset; + } else { + state.light_uniforms[index].shadow_pixel_size = 1.0; + state.light_uniforms[index].shadow_z_far_inv = 1.0; + state.light_uniforms[index].shadow_y_ofs = 0; + } + */ + state.light_uniforms[index].flags = l->blend_mode << LIGHT_FLAGS_BLEND_SHIFT; + state.light_uniforms[index].flags |= l->shadow_filter << LIGHT_FLAGS_FILTER_SHIFT; + /* + if (clight->shadow.enabled) { + state.light_uniforms[index].flags |= LIGHT_FLAGS_HAS_SHADOW; + } + */ + + if (clight->texture.is_valid()) { + Rect2 atlas_rect = GLES3::TextureStorage::get_singleton()->texture_atlas_get_texture_rect(clight->texture); + state.light_uniforms[index].atlas_rect[0] = atlas_rect.position.x; + state.light_uniforms[index].atlas_rect[1] = atlas_rect.position.y; + state.light_uniforms[index].atlas_rect[2] = atlas_rect.size.width; + state.light_uniforms[index].atlas_rect[3] = atlas_rect.size.height; + + } else { + state.light_uniforms[index].atlas_rect[0] = 0; + state.light_uniforms[index].atlas_rect[1] = 0; + state.light_uniforms[index].atlas_rect[2] = 0; + state.light_uniforms[index].atlas_rect[3] = 0; + } + + l->render_index_cache = index; + + index++; + l = l->next_ptr; + } + + light_count = index; + } + + if (light_count > 0) { + glBindBufferBase(GL_UNIFORM_BUFFER, LIGHT_UNIFORM_LOCATION, state.canvas_instance_data_buffers[state.current_buffer].light_ubo); + +#ifdef WEB_ENABLED + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(LightUniform) * light_count, state.light_uniforms); +#else + // On Desktop and mobile we map the memory without synchronizing for maximum speed. + void *ubo = glMapBufferRange(GL_UNIFORM_BUFFER, 0, sizeof(LightUniform) * light_count, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT); + memcpy(ubo, state.light_uniforms, sizeof(LightUniform) * light_count); + glUnmapBuffer(GL_UNIFORM_BUFFER); +#endif + + GLuint texture_atlas = texture_storage->texture_atlas_get_texture(); + if (texture_atlas == 0) { + GLES3::Texture *tex = texture_storage->get_texture(texture_storage->texture_gl_get_default(GLES3::DEFAULT_GL_TEXTURE_WHITE)); + texture_atlas = tex->tex_id; + } + glActiveTexture(GL_TEXTURE0 + GLES3::Config::get_singleton()->max_texture_image_units - 2); + glBindTexture(GL_TEXTURE_2D, texture_atlas); + } { //update canvas state uniform buffer @@ -175,13 +339,12 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ state_buffer.screen_pixel_size[0] = 1.0 / render_target_size.x; state_buffer.screen_pixel_size[1] = 1.0 / render_target_size.y; - // TODO: temporary, this should be set at the top of this function glViewport(0, 0, render_target_size.x, render_target_size.y); state_buffer.time = state.time; state_buffer.use_pixel_snap = p_snap_2d_vertices_to_pixel; - state_buffer.directional_light_count = 0; //directional_light_count; + state_buffer.directional_light_count = directional_light_count; Vector2 canvas_scale = p_canvas_transform.get_scale(); @@ -200,7 +363,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ state_buffer.sdf_to_tex[3] = -sdf_tex_rect.position.y / sdf_tex_rect.size.height; state_buffer.tex_to_sdf = 1.0 / ((canvas_scale.x + canvas_scale.y) * 0.5); - glBindBufferBase(GL_UNIFORM_BUFFER, BASE_UNIFORM_LOCATION, state.canvas_state_buffer); + glBindBufferBase(GL_UNIFORM_BUFFER, BASE_UNIFORM_LOCATION, state.canvas_instance_data_buffers[state.current_buffer].state_ubo); glBufferData(GL_UNIFORM_BUFFER, sizeof(StateBuffer), &state_buffer, GL_STREAM_DRAW); GLuint global_buffer = material_storage->global_shader_parameters_get_uniform_buffer(); @@ -440,7 +603,9 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou GLES3::CanvasMaterialData *material_data = state.canvas_instance_batches[i].material_data; CanvasShaderGLES3::ShaderVariant variant = state.canvas_instance_batches[i].shader_variant; - _bind_material(material_data, variant); + uint64_t specialization = 0; + specialization |= uint64_t(state.canvas_instance_batches[i].lights_disabled); + _bind_material(material_data, variant, specialization); GLES3::CanvasShaderData::BlendMode blend_mode = state.canvas_instance_batches[i].blend_mode; @@ -552,6 +717,38 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, const Tran bool skipping = false; + // TODO: consider making lights a per-batch property and then baking light operations in the shader for better performance. + uint32_t lights[4] = { 0, 0, 0, 0 }; + + uint16_t light_count = 0; + + { + Light *light = p_lights; + + while (light) { + if (light->render_index_cache >= 0 && p_item->light_mask & light->item_mask && p_item->z_final >= light->z_min && p_item->z_final <= light->z_max && p_item->global_rect_cache.intersects_transformed(light->xform_cache, light->rect_cache)) { + uint32_t light_index = light->render_index_cache; + lights[light_count >> 2] |= light_index << ((light_count & 3) * 8); + + light_count++; + + if (light_count == data.max_lights_per_item) { + break; + } + } + light = light->next_ptr; + } + + base_flags |= light_count << FLAGS_LIGHT_COUNT_SHIFT; + } + + bool lights_disabled = light_count == 0 && !state.using_directional_lights; + + if (lights_disabled != state.canvas_instance_batches[state.current_batch_index].lights_disabled) { + _new_batch(r_batch_broken, r_index); + state.canvas_instance_batches[state.current_batch_index].lights_disabled = lights_disabled; + } + const Item::Command *c = p_item->commands; while (c) { if (skipping && c->type != Item::Command::TYPE_ANIMATION_SLICE) { @@ -578,6 +775,11 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, const Tran state.instance_data_array[r_index].pad[0] = 0.0; state.instance_data_array[r_index].pad[1] = 0.0; + state.instance_data_array[r_index].lights[0] = lights[0]; + state.instance_data_array[r_index].lights[1] = lights[1]; + state.instance_data_array[r_index].lights[2] = lights[2]; + state.instance_data_array[r_index].lights[3] = lights[3]; + state.instance_data_array[r_index].flags = base_flags | (state.instance_data_array[r_index == 0 ? 0 : r_index - 1].flags & (FLAGS_DEFAULT_NORMAL_MAP_USED | FLAGS_DEFAULT_SPECULAR_MAP_USED)); //reset on each command for sanity, keep canvastexture binding config Color blend_color; @@ -1138,25 +1340,41 @@ void RasterizerCanvasGLES3::_new_batch(bool &r_batch_broken, uint32_t &r_index) _align_instance_data_buffer(r_index); } -void RasterizerCanvasGLES3::_bind_material(GLES3::CanvasMaterialData *p_material_data, CanvasShaderGLES3::ShaderVariant p_variant) { +void RasterizerCanvasGLES3::_bind_material(GLES3::CanvasMaterialData *p_material_data, CanvasShaderGLES3::ShaderVariant p_variant, uint64_t p_specialization) { if (p_material_data) { if (p_material_data->shader_data->version.is_valid() && p_material_data->shader_data->valid) { // Bind uniform buffer and textures p_material_data->bind_uniforms(); - GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_bind_shader(p_material_data->shader_data->version, p_variant); + GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_bind_shader(p_material_data->shader_data->version, p_variant, p_specialization); } else { - GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_bind_shader(data.canvas_shader_default_version, p_variant); + GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_bind_shader(data.canvas_shader_default_version, p_variant, p_specialization); } } else { - GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_bind_shader(data.canvas_shader_default_version, p_variant); + GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_bind_shader(data.canvas_shader_default_version, p_variant, p_specialization); } } RID RasterizerCanvasGLES3::light_create() { - return RID(); + CanvasLight canvas_light; + return canvas_light_owner.make_rid(canvas_light); } void RasterizerCanvasGLES3::light_set_texture(RID p_rid, RID p_texture) { + GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); + + CanvasLight *cl = canvas_light_owner.get_or_null(p_rid); + ERR_FAIL_COND(!cl); + if (cl->texture == p_texture) { + return; + } + if (cl->texture.is_valid()) { + texture_storage->texture_remove_from_texture_atlas(cl->texture); + } + cl->texture = p_texture; + + if (cl->texture.is_valid()) { + texture_storage->texture_add_to_texture_atlas(cl->texture); + } } void RasterizerCanvasGLES3::light_set_use_shadow(RID p_rid, bool p_enable) { @@ -1185,6 +1403,14 @@ void RasterizerCanvasGLES3::set_shadow_texture_size(int p_size) { } bool RasterizerCanvasGLES3::free(RID p_rid) { + if (canvas_light_owner.owns(p_rid)) { + CanvasLight *cl = canvas_light_owner.get_or_null(p_rid); + ERR_FAIL_COND_V(!cl, false); + canvas_light_owner.free(p_rid); + } else { + return false; + } + return true; } @@ -1355,7 +1581,7 @@ void RasterizerCanvasGLES3::_prepare_canvas_texture(RID p_texture, RS::CanvasIte state.instance_data_array[r_index].flags &= ~FLAGS_DEFAULT_SPECULAR_MAP_USED; } - if (!normal_map) { + if (normal_map) { state.instance_data_array[r_index].flags |= FLAGS_DEFAULT_NORMAL_MAP_USED; } else { state.instance_data_array[r_index].flags &= ~FLAGS_DEFAULT_NORMAL_MAP_USED; @@ -1565,13 +1791,23 @@ void RasterizerCanvasGLES3::free_polygon(PolygonID p_polygon) { // In theory allocations can reach as high as number of windows * 3 frames // because OpenGL can start rendering subsequent frames before finishing the current one void RasterizerCanvasGLES3::_allocate_instance_data_buffer() { - GLuint new_buffer; - glGenBuffers(1, &new_buffer); - glBindBuffer(GL_UNIFORM_BUFFER, new_buffer); - glBufferData(GL_UNIFORM_BUFFER, data.max_instance_buffer_size, nullptr, GL_DYNAMIC_DRAW); + GLuint new_buffers[3]; + glGenBuffers(3, new_buffers); + // Batch UBO. + glBindBuffer(GL_UNIFORM_BUFFER, new_buffers[0]); + glBufferData(GL_UNIFORM_BUFFER, data.max_instance_buffer_size, nullptr, GL_STREAM_DRAW); + // Light uniform buffer. + glBindBuffer(GL_UNIFORM_BUFFER, new_buffers[1]); + glBufferData(GL_UNIFORM_BUFFER, sizeof(LightUniform) * data.max_lights_per_render, nullptr, GL_STREAM_DRAW); + // State buffer. + glBindBuffer(GL_UNIFORM_BUFFER, new_buffers[2]); + glBufferData(GL_UNIFORM_BUFFER, sizeof(StateBuffer), nullptr, GL_STREAM_DRAW); + state.current_buffer = (state.current_buffer + 1); DataBuffer db; - db.ubo = new_buffer; + db.ubo = new_buffers[0]; + db.light_ubo = new_buffers[1]; + db.state_ubo = new_buffers[2]; db.last_frame_used = RSG::rasterizer->get_frame_number(); state.canvas_instance_data_buffers.insert(state.current_buffer, db); state.current_buffer = state.current_buffer % state.canvas_instance_data_buffers.size(); @@ -1753,12 +1989,21 @@ RasterizerCanvasGLES3::RasterizerCanvasGLES3() { state.canvas_instance_batches.reserve(200); for (int i = 0; i < 3; i++) { - GLuint new_buffer; - glGenBuffers(1, &new_buffer); - glBindBuffer(GL_UNIFORM_BUFFER, new_buffer); - glBufferData(GL_UNIFORM_BUFFER, data.max_instance_buffer_size, nullptr, GL_DYNAMIC_DRAW); + GLuint new_buffers[3]; + glGenBuffers(3, new_buffers); + // Batch UBO. + glBindBuffer(GL_UNIFORM_BUFFER, new_buffers[0]); + glBufferData(GL_UNIFORM_BUFFER, data.max_instance_buffer_size, nullptr, GL_STREAM_DRAW); + // Light uniform buffer. + glBindBuffer(GL_UNIFORM_BUFFER, new_buffers[1]); + glBufferData(GL_UNIFORM_BUFFER, sizeof(LightUniform) * data.max_lights_per_render, nullptr, GL_STREAM_DRAW); + // State buffer. + glBindBuffer(GL_UNIFORM_BUFFER, new_buffers[2]); + glBufferData(GL_UNIFORM_BUFFER, sizeof(StateBuffer), nullptr, GL_STREAM_DRAW); DataBuffer db; - db.ubo = new_buffer; + db.ubo = new_buffers[0]; + db.light_ubo = new_buffers[1]; + db.state_ubo = new_buffers[2]; db.last_frame_used = 0; db.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); state.canvas_instance_data_buffers[i] = db; @@ -1766,6 +2011,7 @@ RasterizerCanvasGLES3::RasterizerCanvasGLES3() { glBindBuffer(GL_UNIFORM_BUFFER, 0); state.instance_data_array = memnew_arr(InstanceData, data.max_instances_per_ubo); + state.light_uniforms = memnew_arr(LightUniform, data.max_lights_per_render); { const uint32_t no_of_instances = data.max_instances_per_batch; @@ -1792,14 +2038,9 @@ RasterizerCanvasGLES3::RasterizerCanvasGLES3() { delete[] indices; } - glGenBuffers(1, &state.canvas_state_buffer); - glBindBuffer(GL_UNIFORM_BUFFER, state.canvas_state_buffer); - glBufferData(GL_UNIFORM_BUFFER, sizeof(StateBuffer), nullptr, GL_STREAM_DRAW); - glBindBuffer(GL_UNIFORM_BUFFER, 0); - String global_defines; global_defines += "#define MAX_GLOBAL_SHADER_UNIFORMS 256\n"; // TODO: this is arbitrary for now - global_defines += "#define MAX_LIGHTS " + itos(data.max_instances_per_batch) + "\n"; + global_defines += "#define MAX_LIGHTS " + itos(data.max_lights_per_render) + "\n"; global_defines += "#define MAX_DRAW_DATA_INSTANCES " + itos(data.max_instances_per_batch) + "\n"; GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.initialize(global_defines); @@ -1852,7 +2093,8 @@ RasterizerCanvasGLES3::~RasterizerCanvasGLES3() { glDeleteVertexArrays(1, &data.canvas_quad_array); GLES3::TextureStorage::get_singleton()->canvas_texture_free(default_canvas_texture); - memfree(state.instance_data_array); + memdelete_arr(state.instance_data_array); + memdelete_arr(state.light_uniforms); } #endif // GLES3_ENABLED diff --git a/drivers/gles3/rasterizer_canvas_gles3.h b/drivers/gles3/rasterizer_canvas_gles3.h index 15c2ca5710..65e5f14838 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.h +++ b/drivers/gles3/rasterizer_canvas_gles3.h @@ -96,6 +96,33 @@ class RasterizerCanvasGLES3 : public RendererCanvasRender { DEFAULT_MAX_LIGHTS_PER_RENDER = 256, }; + /******************/ + /**** LIGHTING ****/ + /******************/ + + struct CanvasLight { + RID texture; + }; + + RID_Owner canvas_light_owner; + + struct LightUniform { + float matrix[8]; //light to texture coordinate matrix + float shadow_matrix[8]; //light to shadow coordinate matrix + float color[4]; + + uint8_t shadow_color[4]; + uint32_t flags; //index to light texture + float shadow_pixel_size; + float height; + + float position[2]; + float shadow_z_far_inv; + float shadow_y_ofs; + + float atlas_rect[4]; + }; + public: enum { BASE_UNIFORM_LOCATION = 0, @@ -184,8 +211,8 @@ public: RID canvas_shader_default_version; - uint32_t max_lights_per_render; - uint32_t max_lights_per_item; + uint32_t max_lights_per_render = 256; + uint32_t max_lights_per_item = 16; uint32_t max_instances_per_batch = 512; uint32_t max_instances_per_ubo = 16384; uint32_t max_instance_buffer_size = 16384 * 128; @@ -212,16 +239,22 @@ public: const Item::Command *command = nullptr; Item::Command::Type command_type = Item::Command::TYPE_ANIMATION_SLICE; // Can default to any type that doesn't form a batch. uint32_t primitive_points = 0; + + bool lights_disabled = false; }; + // DataBuffer contains our per-frame data. I.e. the resources that are updated each frame. + // We track them and ensure that they don't get reused until at least 2 frames have passed + // to avoid the GPU stalling to wait for a resource to become available. struct DataBuffer { GLuint ubo = 0; + GLuint light_ubo = 0; + GLuint state_ubo = 0; uint64_t last_frame_used = -3; GLsync fence = GLsync(); }; struct State { - GLuint canvas_state_buffer; LocalVector canvas_instance_data_buffers; LocalVector canvas_instance_batches; uint32_t current_buffer = 0; @@ -230,6 +263,10 @@ public: InstanceData *instance_data_array = nullptr; + LightUniform *light_uniforms = nullptr; + + bool using_directional_lights = false; + RID current_tex = RID(); RS::CanvasItemTextureFilter current_filter_mode = RS::CANVAS_ITEM_TEXTURE_FILTER_MAX; RS::CanvasItemTextureRepeat current_repeat_mode = RS::CANVAS_ITEM_TEXTURE_REPEAT_MAX; @@ -282,7 +319,7 @@ public: void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, uint32_t &r_last_index, bool p_to_backbuffer = false); void _record_item_commands(const Item *p_item, const Transform2D &p_canvas_transform_inverse, Item *¤t_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_break_batch); void _render_batch(Light *p_lights, uint32_t p_index); - void _bind_material(GLES3::CanvasMaterialData *p_material_data, CanvasShaderGLES3::ShaderVariant p_variant); + void _bind_material(GLES3::CanvasMaterialData *p_material_data, CanvasShaderGLES3::ShaderVariant p_variant, uint64_t p_specialization); void _new_batch(bool &r_batch_broken, uint32_t &r_index); void _add_to_batch(uint32_t &r_index, bool &r_batch_broken); void _allocate_instance_data_buffer(); diff --git a/drivers/gles3/shaders/canvas.glsl b/drivers/gles3/shaders/canvas.glsl index a177112476..22a11cdc29 100644 --- a/drivers/gles3/shaders/canvas.glsl +++ b/drivers/gles3/shaders/canvas.glsl @@ -211,7 +211,7 @@ void main() { #include "canvas_uniforms_inc.glsl" #include "stdlib_inc.glsl" -//uniform sampler2D atlas_texture; //texunit:-2 +uniform sampler2D atlas_texture; //texunit:-2 //uniform sampler2D shadow_atlas_texture; //texunit:-3 uniform sampler2D screen_texture; //texunit:-4 uniform sampler2D sdf_texture; //texunit:-5 @@ -243,6 +243,77 @@ layout(std140) uniform MaterialUniforms{ #endif #GLOBALS +#ifndef DISABLE_LIGHTING +#ifdef LIGHT_CODE_USED + +vec4 light_compute( + vec3 light_vertex, + vec3 light_position, + vec3 normal, + vec4 light_color, + float light_energy, + vec4 specular_shininess, + inout vec4 shadow_modulate, + vec2 screen_uv, + vec2 uv, + vec4 color, bool is_directional) { + vec4 light = vec4(0.0); + vec3 light_direction = vec3(0.0); + + if (is_directional) { + light_direction = normalize(mix(vec3(light_position.xy, 0.0), vec3(0, 0, 1), light_position.z)); + light_position = vec3(0.0); + } else { + light_direction = normalize(light_position - light_vertex); + } + +#CODE : LIGHT + + return light; +} + +#endif + +vec3 light_normal_compute(vec3 light_vec, vec3 normal, vec3 base_color, vec3 light_color, vec4 specular_shininess, bool specular_shininess_used) { + float cNdotL = max(0.0, dot(normal, light_vec)); + + if (specular_shininess_used) { + //blinn + vec3 view = vec3(0.0, 0.0, 1.0); // not great but good enough + vec3 half_vec = normalize(view + light_vec); + + float cNdotV = max(dot(normal, view), 0.0); + float cNdotH = max(dot(normal, half_vec), 0.0); + float cVdotH = max(dot(view, half_vec), 0.0); + float cLdotH = max(dot(light_vec, half_vec), 0.0); + float shininess = exp2(15.0 * specular_shininess.a + 1.0) * 0.25; + float blinn = pow(cNdotH, shininess); + blinn *= (shininess + 8.0) * (1.0 / (8.0 * M_PI)); + float s = (blinn) / max(4.0 * cNdotV * cNdotL, 0.75); + + return specular_shininess.rgb * light_color * s + light_color * base_color * cNdotL; + } else { + return light_color * base_color * cNdotL; + } +} + +void light_blend_compute(uint light_base, vec4 light_color, inout vec3 color) { + uint blend_mode = light_array[light_base].flags & LIGHT_FLAGS_BLEND_MASK; + + switch (blend_mode) { + case LIGHT_FLAGS_BLEND_MODE_ADD: { + color.rgb += light_color.rgb * light_color.a; + } break; + case LIGHT_FLAGS_BLEND_MODE_SUB: { + color.rgb -= light_color.rgb * light_color.a; + } break; + case LIGHT_FLAGS_BLEND_MODE_MIX: { + color.rgb = mix(color.rgb, light_color.rgb, light_color.a); + } break; + } +} + +#endif #ifdef USE_NINEPATCH @@ -353,7 +424,8 @@ void main() { color *= texture(color_texture, uv); } - bool using_light = false; + uint light_count = (draw_data[draw_data_instance].flags >> uint(FLAGS_LIGHT_COUNT_SHIFT)) & uint(0xF); //max 16 lights + bool using_light = light_count > 0u || directional_light_count > 0u; vec3 normal; @@ -414,11 +486,105 @@ void main() { #endif } + if (normal_used) { + //convert by item transform + normal.xy = mat2(normalize(draw_data[draw_data_instance].world_x), normalize(draw_data[draw_data_instance].world_y)) * normal.xy; + //convert by canvas transform + normal = normalize((canvas_normal_transform * vec4(normal, 0.0)).xyz); + } + + vec4 base_color = color; + #ifdef MODE_LIGHT_ONLY color = vec4(0.0); #else color *= canvas_modulation; #endif +#if !defined(DISABLE_LIGHTING) && !defined(MODE_UNSHADED) + + // Directional Lights + + for (uint i = 0u; i < directional_light_count; i++) { + uint light_base = i; + + vec2 direction = light_array[light_base].position; + vec4 light_color = light_array[light_base].color; + +#ifdef LIGHT_CODE_USED + + vec4 shadow_modulate = vec4(1.0); + light_color = light_compute(light_vertex, vec3(direction, light_array[light_base].height), normal, light_color, light_color.a, specular_shininess, shadow_modulate, screen_uv, uv, base_color, true); +#else + + if (normal_used) { + vec3 light_vec = normalize(mix(vec3(direction, 0.0), vec3(0, 0, 1), light_array[light_base].height)); + light_color.rgb = light_normal_compute(light_vec, normal, base_color.rgb, light_color.rgb, specular_shininess, specular_shininess_used); + } else { + light_color.rgb *= base_color.rgb; + } +#endif + + light_blend_compute(light_base, light_color, color.rgb); + } + + // Positional Lights + + for (uint i = 0u; i < MAX_LIGHTS_PER_ITEM; i++) { + if (i >= light_count) { + break; + } + uint light_base; + if (i < 8u) { + if (i < 4u) { + light_base = draw_data[draw_data_instance].lights[0]; + } else { + light_base = draw_data[draw_data_instance].lights[1]; + } + } else { + if (i < 12u) { + light_base = draw_data[draw_data_instance].lights[2]; + } else { + light_base = draw_data[draw_data_instance].lights[3]; + } + } + light_base >>= (i & 3u) * 8u; + light_base &= uint(0xFF); + + vec2 tex_uv = (vec4(vertex, 0.0, 1.0) * mat4(light_array[light_base].texture_matrix[0], light_array[light_base].texture_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations. + vec2 tex_uv_atlas = tex_uv * light_array[light_base].atlas_rect.zw + light_array[light_base].atlas_rect.xy; + vec4 light_color = textureLod(atlas_texture, tex_uv_atlas, 0.0); + vec4 light_base_color = light_array[light_base].color; + +#ifdef LIGHT_CODE_USED + + vec4 shadow_modulate = vec4(1.0); + vec3 light_position = vec3(light_array[light_base].position, light_array[light_base].height); + + light_color.rgb *= light_base_color.rgb; + light_color = light_compute(light_vertex, light_position, normal, light_color, light_base_color.a, specular_shininess, shadow_modulate, screen_uv, uv, base_color, false); +#else + + light_color.rgb *= light_base_color.rgb * light_base_color.a; + + if (normal_used) { + vec3 light_pos = vec3(light_array[light_base].position, light_array[light_base].height); + vec3 pos = light_vertex; + vec3 light_vec = normalize(light_pos - pos); + + light_color.rgb = light_normal_compute(light_vec, normal, base_color.rgb, light_color.rgb, specular_shininess, specular_shininess_used); + } else { + light_color.rgb *= base_color.rgb; + } +#endif + if (any(lessThan(tex_uv, vec2(0.0, 0.0))) || any(greaterThanEqual(tex_uv, vec2(1.0, 1.0)))) { + //if outside the light texture, light color is zero + light_color.a = 0.0; + } + + light_blend_compute(light_base, light_color, color.rgb); + } +#endif + frag_color = color; } diff --git a/drivers/gles3/shaders/canvas_uniforms_inc.glsl b/drivers/gles3/shaders/canvas_uniforms_inc.glsl index 6b65e09cbf..43d275205f 100644 --- a/drivers/gles3/shaders/canvas_uniforms_inc.glsl +++ b/drivers/gles3/shaders/canvas_uniforms_inc.glsl @@ -94,6 +94,27 @@ layout(std140) uniform CanvasData { //ubo:0 #define LIGHT_FLAGS_SHADOW_PCF5 uint(1 << 22) #define LIGHT_FLAGS_SHADOW_PCF13 uint(2 << 22) +struct Light { + mat2x4 texture_matrix; //light to texture coordinate matrix (transposed) + mat2x4 shadow_matrix; //light to shadow coordinate matrix (transposed) + vec4 color; + + uint shadow_color; // packed + uint flags; //index to light texture + float shadow_pixel_size; + float height; + + vec2 position; + float shadow_zfar_inv; + float shadow_y_ofs; + + vec4 atlas_rect; +}; + +layout(std140) uniform LightData { //ubo:2 + Light light_array[MAX_LIGHTS]; +}; + layout(std140) uniform DrawDataInstances { //ubo:3 DrawData draw_data[MAX_DRAW_DATA_INSTANCES]; diff --git a/drivers/gles3/shaders/copy.glsl b/drivers/gles3/shaders/copy.glsl index ca2fc7e36d..796ba79c2e 100644 --- a/drivers/gles3/shaders/copy.glsl +++ b/drivers/gles3/shaders/copy.glsl @@ -2,7 +2,7 @@ #[modes] mode_default = #define MODE_SIMPLE_COPY -mode_copy_section = #define USE_COPY_SECTION +mode_copy_section = #define USE_COPY_SECTION \n#define MODE_SIMPLE_COPY mode_gaussian_blur = #define MODE_GAUSSIAN_BLUR mode_mipmap = #define MODE_MIPMAP mode_simple_color = #define MODE_SIMPLE_COLOR \n#define USE_COPY_SECTION @@ -25,8 +25,7 @@ void main() { gl_Position = vec4(vertex_attrib, 1.0, 1.0); #ifdef USE_COPY_SECTION - gl_Position.xy = (copy_section.xy + (uv_interp.xy * 0.5 + 0.5) * copy_section.zw) * 2.0 - 1.0; - uv_interp = copy_section.xy + uv_interp * copy_section.zw; + gl_Position.xy = (copy_section.xy + uv_interp.xy * copy_section.zw) * 2.0 - 1.0; #endif } diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index 442bd69b55..48f460f995 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -197,6 +197,22 @@ TextureStorage::TextureStorage() { glBindTexture(GL_TEXTURE_2D, 0); + { // Atlas Texture initialize. + uint8_t pixel_data[4 * 4 * 4]; + for (int i = 0; i < 16; i++) { + pixel_data[i * 4 + 0] = 0; + pixel_data[i * 4 + 1] = 0; + pixel_data[i * 4 + 2] = 0; + pixel_data[i * 4 + 3] = 255; + } + + glGenTextures(1, &texture_atlas.texture); + glBindTexture(GL_TEXTURE_2D, texture_atlas.texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixel_data); + } + + glBindTexture(GL_TEXTURE_2D, 0); + #ifdef GLES_OVER_GL glEnable(GL_PROGRAM_POINT_SIZE); #endif @@ -207,6 +223,11 @@ TextureStorage::~TextureStorage() { for (int i = 0; i < DEFAULT_GL_TEXTURE_MAX; i++) { texture_free(default_gl_textures[i]); } + + glDeleteTextures(1, &texture_atlas.texture); + texture_atlas.texture = 0; + glDeleteFramebuffers(1, &texture_atlas.framebuffer); + texture_atlas.framebuffer = 0; } //TODO, move back to storage @@ -653,7 +674,7 @@ void TextureStorage::texture_free(RID p_texture) { } } - //decal_atlas_remove_texture(p_texture); + texture_atlas_remove_texture(p_texture); for (int i = 0; i < t->proxies.size(); i++) { Texture *p = texture_owner.get_or_null(t->proxies[i]); @@ -875,7 +896,7 @@ void TextureStorage::texture_replace(RID p_texture, RID p_by_texture) { //delete last, so proxies can be updated texture_owner.free(p_by_texture); - //decal_atlas_mark_dirty_on_texture(p_texture); + texture_atlas_mark_dirty_on_texture(p_texture); } void TextureStorage::texture_set_size_override(RID p_texture, int p_width, int p_height) { @@ -1143,6 +1164,217 @@ RID TextureStorage::texture_create_radiance_cubemap(RID p_source, int p_resoluti return RID(); } +/* TEXTURE ATLAS API */ + +void TextureStorage::texture_add_to_texture_atlas(RID p_texture) { + if (!texture_atlas.textures.has(p_texture)) { + TextureAtlas::Texture t; + t.users = 1; + texture_atlas.textures[p_texture] = t; + texture_atlas.dirty = true; + } else { + TextureAtlas::Texture *t = texture_atlas.textures.getptr(p_texture); + t->users++; + } +} + +void TextureStorage::texture_remove_from_texture_atlas(RID p_texture) { + TextureAtlas::Texture *t = texture_atlas.textures.getptr(p_texture); + ERR_FAIL_COND(!t); + t->users--; + if (t->users == 0) { + texture_atlas.textures.erase(p_texture); + // Do not mark it dirty, there is no need to since it remains working. + } +} + +void TextureStorage::texture_atlas_mark_dirty_on_texture(RID p_texture) { + if (texture_atlas.textures.has(p_texture)) { + texture_atlas.dirty = true; // Mark it dirty since it was most likely modified. + } +} + +void TextureStorage::texture_atlas_remove_texture(RID p_texture) { + if (texture_atlas.textures.has(p_texture)) { + texture_atlas.textures.erase(p_texture); + // There is not much a point of making it dirty, texture can be removed next time the atlas is updated. + } +} + +GLuint TextureStorage::texture_atlas_get_texture() const { + return texture_atlas.texture; +} + +void TextureStorage::update_texture_atlas() { + CopyEffects *copy_effects = CopyEffects::get_singleton(); + ERR_FAIL_NULL(copy_effects); + + if (!texture_atlas.dirty) { + return; //nothing to do + } + + texture_atlas.dirty = false; + + if (texture_atlas.texture != 0) { + glDeleteTextures(1, &texture_atlas.texture); + texture_atlas.texture = 0; + glDeleteFramebuffers(1, &texture_atlas.framebuffer); + texture_atlas.framebuffer = 0; + } + + const int border = 2; + + if (texture_atlas.textures.size()) { + //generate atlas + Vector itemsv; + itemsv.resize(texture_atlas.textures.size()); + int base_size = 8; + + int idx = 0; + + for (const KeyValue &E : texture_atlas.textures) { + TextureAtlas::SortItem &si = itemsv.write[idx]; + + Texture *src_tex = get_texture(E.key); + + si.size.width = (src_tex->width / border) + 1; + si.size.height = (src_tex->height / border) + 1; + si.pixel_size = Size2i(src_tex->width, src_tex->height); + + if (base_size < si.size.width) { + base_size = nearest_power_of_2_templated(si.size.width); + } + + si.texture = E.key; + idx++; + } + + //sort items by size + itemsv.sort(); + + //attempt to create atlas + int item_count = itemsv.size(); + TextureAtlas::SortItem *items = itemsv.ptrw(); + + int atlas_height = 0; + + while (true) { + Vector v_offsetsv; + v_offsetsv.resize(base_size); + + int *v_offsets = v_offsetsv.ptrw(); + memset(v_offsets, 0, sizeof(int) * base_size); + + int max_height = 0; + + for (int i = 0; i < item_count; i++) { + //best fit + TextureAtlas::SortItem &si = items[i]; + int best_idx = -1; + int best_height = 0x7FFFFFFF; + for (int j = 0; j <= base_size - si.size.width; j++) { + int height = 0; + for (int k = 0; k < si.size.width; k++) { + int h = v_offsets[k + j]; + if (h > height) { + height = h; + if (height > best_height) { + break; //already bad + } + } + } + + if (height < best_height) { + best_height = height; + best_idx = j; + } + } + + //update + for (int k = 0; k < si.size.width; k++) { + v_offsets[k + best_idx] = best_height + si.size.height; + } + + si.pos.x = best_idx; + si.pos.y = best_height; + + if (si.pos.y + si.size.height > max_height) { + max_height = si.pos.y + si.size.height; + } + } + + if (max_height <= base_size * 2) { + atlas_height = max_height; + break; //good ratio, break; + } + + base_size *= 2; + } + + texture_atlas.size.width = base_size * border; + texture_atlas.size.height = nearest_power_of_2_templated(atlas_height * border); + + for (int i = 0; i < item_count; i++) { + TextureAtlas::Texture *t = texture_atlas.textures.getptr(items[i].texture); + t->uv_rect.position = items[i].pos * border + Vector2i(border / 2, border / 2); + t->uv_rect.size = items[i].pixel_size; + + t->uv_rect.position /= Size2(texture_atlas.size); + t->uv_rect.size /= Size2(texture_atlas.size); + } + } else { + texture_atlas.size.width = 4; + texture_atlas.size.height = 4; + } + + { // Atlas Texture initialize. + // TODO validate texture atlas size with maximum texture size + glGenTextures(1, &texture_atlas.texture); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture_atlas.texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, texture_atlas.size.width, texture_atlas.size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + + glGenFramebuffers(1, &texture_atlas.framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, texture_atlas.framebuffer); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_atlas.texture, 0); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (status != GL_FRAMEBUFFER_COMPLETE) { + glDeleteFramebuffers(1, &texture_atlas.framebuffer); + texture_atlas.framebuffer = 0; + glDeleteTextures(1, &texture_atlas.texture); + texture_atlas.texture = 0; + WARN_PRINT("Could not create texture atlas, status: " + get_framebuffer_error(status)); + return; + } + glViewport(0, 0, texture_atlas.size.width, texture_atlas.size.height); + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + glBindTexture(GL_TEXTURE_2D, 0); + } + + glDisable(GL_BLEND); + + if (texture_atlas.textures.size()) { + for (const KeyValue &E : texture_atlas.textures) { + TextureAtlas::Texture *t = texture_atlas.textures.getptr(E.key); + Texture *src_tex = get_texture(E.key); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, src_tex->tex_id); + copy_effects->copy_to_rect(t->uv_rect); + } + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + /* DECAL API */ RID TextureStorage::decal_allocate() { diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h index 7e083e48e8..39a74236e5 100644 --- a/drivers/gles3/storage/texture_storage.h +++ b/drivers/gles3/storage/texture_storage.h @@ -371,6 +371,38 @@ private: Ref _get_gl_image_and_format(const Ref &p_image, Image::Format p_format, Image::Format &r_real_format, GLenum &r_gl_format, GLenum &r_gl_internal_format, GLenum &r_gl_type, bool &r_compressed, bool p_force_decompress) const; + /* TEXTURE ATLAS API */ + + struct TextureAtlas { + struct Texture { + int users; + Rect2 uv_rect; + }; + + struct SortItem { + RID texture; + Size2i pixel_size; + Size2i size; + Point2i pos; + + bool operator<(const SortItem &p_item) const { + //sort larger to smaller + if (size.height == p_item.size.height) { + return size.width > p_item.size.width; + } else { + return size.height > p_item.size.height; + } + } + }; + + HashMap textures; + bool dirty = true; + + GLuint texture = 0; + GLuint framebuffer = 0; + Size2i size; + } texture_atlas; + /* Render Target API */ mutable RID_Owner render_target_owner; @@ -473,6 +505,25 @@ public: void texture_bind(RID p_texture, uint32_t p_texture_no); RID texture_create_radiance_cubemap(RID p_source, int p_resolution = -1) const; + /* TEXTURE ATLAS API */ + + void update_texture_atlas(); + + GLuint texture_atlas_get_texture() const; + _FORCE_INLINE_ Rect2 texture_atlas_get_texture_rect(RID p_texture) { + TextureAtlas::Texture *t = texture_atlas.textures.getptr(p_texture); + if (!t) { + return Rect2(); + } + + return t->uv_rect; + } + + void texture_add_to_texture_atlas(RID p_texture); + void texture_remove_from_texture_atlas(RID p_texture); + void texture_atlas_mark_dirty_on_texture(RID p_texture); + void texture_atlas_remove_texture(RID p_texture); + /* DECAL API */ virtual RID decal_allocate() override; diff --git a/drivers/gles3/storage/utilities.cpp b/drivers/gles3/storage/utilities.cpp index 16bacf1829..6e91f38050 100644 --- a/drivers/gles3/storage/utilities.cpp +++ b/drivers/gles3/storage/utilities.cpp @@ -302,6 +302,7 @@ void Utilities::update_dirty_resources() { MaterialStorage::get_singleton()->_update_queued_materials(); //MeshStorage::get_singleton()->_update_dirty_skeletons(); MeshStorage::get_singleton()->_update_dirty_multimeshes(); + TextureStorage::get_singleton()->update_texture_atlas(); } void Utilities::set_debug_generate_wireframes(bool p_generate) { -- cgit v1.2.3