From d7b3e0030026762df9d220940265b8fb13e8628b Mon Sep 17 00:00:00 2001 From: VIFEX Date: Tue, 27 Jan 2026 16:11:05 +0800 Subject: [PATCH] feat(nanovg): add draw 3D support (#9571) Signed-off-by: pengyiqiang --- src/draw/nanovg/lv_draw_nanovg.c | 9 ++ src/draw/nanovg/lv_draw_nanovg_3d.c | 82 ++++++++++++++++ src/draw/nanovg/lv_draw_nanovg_private.h | 11 +++ src/drivers/opengles/lv_opengles_driver.c | 18 ++++ src/drivers/opengles/lv_opengles_driver.h | 6 ++ .../gltf/gltf_data/lv_gltf_data_injest.cpp | 37 ++++++- .../gltf/gltf_view/lv_gltf_view_internal.h | 35 +++++++ .../gltf/gltf_view/lv_gltf_view_render.cpp | 97 ++++++++++++++++++- 8 files changed, 288 insertions(+), 7 deletions(-) create mode 100644 src/draw/nanovg/lv_draw_nanovg_3d.c diff --git a/src/draw/nanovg/lv_draw_nanovg.c b/src/draw/nanovg/lv_draw_nanovg.c index 88b3020fc5..13470ea655 100644 --- a/src/draw/nanovg/lv_draw_nanovg.c +++ b/src/draw/nanovg/lv_draw_nanovg.c @@ -204,6 +204,12 @@ static void draw_execute(lv_draw_nanovg_unit_t * u, lv_draw_task_t * t) lv_draw_nanovg_vector(t, t->draw_dsc); break; #endif + +#if LV_USE_3DTEXTURE + case LV_DRAW_TASK_TYPE_3D: + lv_draw_nanovg_3d(t, t->draw_dsc, &t->area); + break; +#endif default: LV_LOG_ERROR("unknown draw task type: %d", t->type); break; @@ -381,6 +387,9 @@ static int32_t draw_evaluate(lv_draw_unit_t * draw_unit, lv_draw_task_t * task) case LV_DRAW_TASK_TYPE_MASK_RECTANGLE: #if LV_USE_VECTOR_GRAPHIC case LV_DRAW_TASK_TYPE_VECTOR: +#endif +#if LV_USE_3DTEXTURE + case LV_DRAW_TASK_TYPE_3D: #endif break; diff --git a/src/draw/nanovg/lv_draw_nanovg_3d.c b/src/draw/nanovg/lv_draw_nanovg_3d.c new file mode 100644 index 0000000000..81b4c7420c --- /dev/null +++ b/src/draw/nanovg/lv_draw_nanovg_3d.c @@ -0,0 +1,82 @@ +/** + * @file lv_draw_nanovg_3d.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_draw_nanovg_private.h" + +#if LV_USE_DRAW_NANOVG && LV_USE_3DTEXTURE + +#include "../../draw/lv_draw_3d.h" +#include "../../drivers/opengles/lv_opengles_driver.h" +#include "../../drivers/opengles/lv_opengles_private.h" +#include "lv_nanovg_utils.h" +#include "lv_nanovg_fbo_cache.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_draw_nanovg_3d(lv_draw_task_t * t, const lv_draw_3d_dsc_t * dsc, const lv_area_t * coords) +{ + LV_PROFILER_DRAW_BEGIN; + + lv_draw_nanovg_unit_t * u = (lv_draw_nanovg_unit_t *)t->draw_unit; + + /* End NanoVG frame temporarily to allow direct OpenGL rendering */ + lv_nanovg_end_frame(u); + + lv_layer_t * layer = t->target_layer; + + /* Get target layer info */ + int32_t layer_w = lv_area_get_width(&layer->buf_area); + int32_t layer_h = lv_area_get_height(&layer->buf_area); + + /* Calculate destination area relative to layer */ + lv_area_t dest_area = *coords; + lv_area_move(&dest_area, -layer->buf_area.x1, -layer->buf_area.y1); + + /* Calculate clip area relative to layer */ + lv_area_t clip_area = t->clip_area; + lv_area_move(&clip_area, -layer->buf_area.x1, -layer->buf_area.y1); + + /* Reinitialize OpenGL ES driver state after NanoVG modified it */ + lv_opengles_reinit_state(); + + /* Use LVGL's OpenGL ES rendering infrastructure */ + lv_opengles_viewport(0, 0, layer_w, layer_h); + lv_opengles_render_texture_rbswap(dsc->tex_id, &dest_area, dsc->opa, layer_w, layer_h, + &clip_area, dsc->h_flip, !dsc->v_flip); + + LV_PROFILER_DRAW_END; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +#endif /* LV_USE_DRAW_NANOVG && LV_USE_3DTEXTURE */ diff --git a/src/draw/nanovg/lv_draw_nanovg_private.h b/src/draw/nanovg/lv_draw_nanovg_private.h index a2b3610177..29e17332a1 100644 --- a/src/draw/nanovg/lv_draw_nanovg_private.h +++ b/src/draw/nanovg/lv_draw_nanovg_private.h @@ -22,6 +22,7 @@ extern "C" { #include "../../draw/lv_draw_vector.h" #include "../../draw/lv_draw_arc.h" #include "../../draw/lv_draw_rect.h" +#include "../../draw/lv_draw_3d.h" #include "../../draw/lv_draw_image.h" #include "../../draw/lv_draw_label.h" #include "../../draw/lv_draw_line.h" @@ -84,6 +85,16 @@ typedef struct _lv_draw_nanovg_unit_t { * GLOBAL PROTOTYPES **********************/ +#if LV_USE_3DTEXTURE +/** + * Draw 3D texture on a NanoVG context + * @param t pointer to a drawing task + * @param dsc pointer to a 3D draw descriptor + * @param coords the coordinates of the 3D texture + */ +void lv_draw_nanovg_3d(lv_draw_task_t * t, const lv_draw_3d_dsc_t * dsc, const lv_area_t * coords); +#endif + /** * Draw arc on a NanoVG context * @param t pointer to a drawing task diff --git a/src/drivers/opengles/lv_opengles_driver.c b/src/drivers/opengles/lv_opengles_driver.c index db872cc7c9..0814729f5b 100644 --- a/src/drivers/opengles/lv_opengles_driver.c +++ b/src/drivers/opengles/lv_opengles_driver.c @@ -239,6 +239,24 @@ void lv_opengles_viewport(int32_t x, int32_t y, int32_t w, int32_t h) LV_PROFILER_DRAW_END; } +void lv_opengles_reinit_state(void) +{ + LV_PROFILER_DRAW_BEGIN; + + /* Rebind VAO, VBO, IBO to restore state after NanoVG or other external GL operations */ + lv_opengles_vertex_array_bind(); + lv_opengles_vertex_buffer_bind(); + lv_opengles_index_buffer_bind(); + + /* Re-setup vertex attributes since NanoVG may have modified them */ + for(unsigned int i = 0; i < 2; i++) { + GL_CALL(glEnableVertexAttribArray(i)); + GL_CALL(glVertexAttribPointer(i, 2, GL_FLOAT, GL_FALSE, 16, (const void *)(intptr_t)(i * 2 * 4))); + } + + LV_PROFILER_DRAW_END; +} + void lv_opengles_render(unsigned int texture, const lv_area_t * texture_area, lv_opa_t opa, int32_t disp_w, int32_t disp_h, const lv_area_t * texture_clip_area, bool h_flip, bool v_flip, lv_color_t fill_color, bool blend_opt, bool swap_red_blue) diff --git a/src/drivers/opengles/lv_opengles_driver.h b/src/drivers/opengles/lv_opengles_driver.h index 4034d978e6..f352ec926e 100644 --- a/src/drivers/opengles/lv_opengles_driver.h +++ b/src/drivers/opengles/lv_opengles_driver.h @@ -89,6 +89,12 @@ void lv_opengles_render_clear(void); */ void lv_opengles_viewport(int32_t x, int32_t y, int32_t w, int32_t h); +/** + * Reinitialize OpenGL state after external GL operations (e.g., NanoVG) + * This rebinds VAO, VBO, IBO and resets vertex attributes + */ +void lv_opengles_reinit_state(void); + /********************** * MACROS **********************/ diff --git a/src/libs/gltf/gltf_data/lv_gltf_data_injest.cpp b/src/libs/gltf/gltf_data/lv_gltf_data_injest.cpp index f30446798e..dda74251fb 100644 --- a/src/libs/gltf/gltf_data/lv_gltf_data_injest.cpp +++ b/src/libs/gltf/gltf_data/lv_gltf_data_injest.cpp @@ -104,6 +104,33 @@ static inline GLsizei get_level_count(int32_t width, int32_t height) return static_cast(1 + floor(log2(width > height ? width : height))); } +/** + * @brief Allocate immutable texture storage with fallback for GLES2 + * + * glTexStorage2D (GL_EXT_texture_storage) may not be available on all GLES2 drivers. + * This function falls back to glTexImage2D when the extension is not available. + */ +static inline void tex_storage_2d_compat(GLenum target, GLsizei levels, GLenum internalformat, + GLsizei width, GLsizei height) +{ +#ifdef glTexStorage2D + if(glad_glTexStorage2DEXT) { + glTexStorage2D(target, levels, internalformat, width, height); + return; + } +#endif + /* Fallback: use glTexImage2D for each mipmap level */ + GLenum format = GL_RGBA; + if(internalformat == GL_RGB8) { + format = GL_RGB; + } + for(GLsizei level = 0; level < levels; level++) { + glTexImage2D(target, level, internalformat, width, height, 0, format, GL_UNSIGNED_BYTE, NULL); + width = (width > 1) ? (width / 2) : 1; + height = (height > 1) ? (height / 2) : 1; + } +} + static void load_mesh_texture_impl(lv_gltf_model_t * data, const fastgltf::TextureInfo & material_prop, GLuint * primitive_tex_prop, GLint * primitive_tex_uv_id); @@ -490,7 +517,7 @@ bool injest_image(lv_opengl_shader_manager_t * shader_manager, lv_gltf_model_t * LV_LOG_TRACE("Loading image: %s", image.name.c_str()); const std::string path(file_path.uri.path().begin(), file_path.uri.path().end()); unsigned char * data = stbi_load(path.c_str(), &width, &height, &nrChannels, 4); - glTexStorage2D(GL_TEXTURE_2D, get_level_count(width, height), GL_RGBA8, width, height); + tex_storage_2d_compat(GL_TEXTURE_2D, get_level_count(width, height), GL_RGBA8, width, height); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); stbi_image_free(data); }, @@ -502,7 +529,7 @@ bool injest_image(lv_opengl_shader_manager_t * shader_manager, lv_gltf_model_t * unsigned char * data = stbi_load_from_memory( reinterpret_cast(vector.bytes.data()), static_cast(vector.bytes.size()), &width, &height, &nrChannels, 4); - glTexStorage2D(GL_TEXTURE_2D, get_level_count(width, height), GL_RGBA8, width, height); + tex_storage_2d_compat(GL_TEXTURE_2D, get_level_count(width, height), GL_RGBA8, width, height); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); stbi_image_free(data); }, @@ -562,7 +589,7 @@ static bool injest_image_from_buffer_view(lv_gltf_model_t * data, fastgltf::sour return true; } LV_LOG_TRACE("[WEBP] width: %d height: %d", width, height); - glTexStorage2D(GL_TEXTURE_2D, get_level_count(width, height), GL_RGBA8, width, height); + tex_storage_2d_compat(GL_TEXTURE_2D, get_level_count(width, height), GL_RGBA8, width, height); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); stbi_image_free(data); return false; @@ -580,7 +607,7 @@ static bool injest_image_from_buffer_view(lv_gltf_model_t * data, fastgltf::sour uint8_t * unpacked = WebPDecodeRGBA( reinterpret_cast(vector.bytes.data() + buffer_view.byteOffset), static_cast(buffer_view.byteLength), &width, &height); - glTexStorage2D(GL_TEXTURE_2D, get_level_count(width, height), GL_RGBA8, width, height); + tex_storage_2d_compat(GL_TEXTURE_2D, get_level_count(width, height), GL_RGBA8, width, height); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, unpacked); WebPFree(unpacked); @@ -589,7 +616,7 @@ static bool injest_image_from_buffer_view(lv_gltf_model_t * data, fastgltf::sour uint8_t * unpacked = WebPDecodeRGB( reinterpret_cast(vector.bytes.data() + buffer_view.byteOffset), static_cast(buffer_view.byteLength), &width, &height); - glTexStorage2D(GL_TEXTURE_2D, get_level_count(width, height), GL_RGB8, width, height); + tex_storage_2d_compat(GL_TEXTURE_2D, get_level_count(width, height), GL_RGB8, width, height); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, unpacked); WebPFree(unpacked); diff --git a/src/libs/gltf/gltf_view/lv_gltf_view_internal.h b/src/libs/gltf/gltf_view/lv_gltf_view_internal.h index 3463089e49..559a4a7bd7 100644 --- a/src/libs/gltf/gltf_view/lv_gltf_view_internal.h +++ b/src/libs/gltf/gltf_view/lv_gltf_view_internal.h @@ -104,10 +104,45 @@ typedef struct { } lv_gltf_view_desc_t; typedef struct { + /* Blend state */ GLboolean blend_enabled; GLint blend_src; GLint blend_dst; GLint blend_equation; + + /* Depth state */ + GLboolean depth_test_enabled; + GLboolean depth_mask; + GLint depth_func; + + /* Face culling state */ + GLboolean cull_face_enabled; + GLint cull_face_mode; + GLint front_face; + + /* Stencil state */ + GLboolean stencil_test_enabled; + GLuint stencil_mask; + GLint stencil_func; + GLint stencil_ref; + GLuint stencil_value_mask; + + /* Buffer bindings */ + GLuint current_vao; + GLuint current_vbo; + GLuint current_ibo; + GLuint current_program; + + /* Texture state */ + GLint active_texture; + GLuint bound_texture_2d; + + /* Viewport and scissor */ + GLint viewport[4]; + GLboolean scissor_test_enabled; + GLint scissor_box[4]; + + /* Clear values */ GLfloat clear_depth; GLfloat clear_color[4]; } lv_opengl_state_t; diff --git a/src/libs/gltf/gltf_view/lv_gltf_view_render.cpp b/src/libs/gltf/gltf_view/lv_gltf_view_render.cpp index 8ff13bba8f..fb012d168e 100644 --- a/src/libs/gltf/gltf_view/lv_gltf_view_render.cpp +++ b/src/libs/gltf/gltf_view/lv_gltf_view_render.cpp @@ -168,17 +168,59 @@ GLuint lv_gltf_view_render(lv_gltf_t * viewer) static void lv_gltf_view_push_opengl_state(lv_opengl_state_t * state) { + /* Blend state */ GL_CALL(glGetBooleanv(GL_BLEND, &state->blend_enabled)); GL_CALL(glGetIntegerv(GL_BLEND_SRC_ALPHA, &state->blend_src)); GL_CALL(glGetIntegerv(GL_BLEND_DST_ALPHA, &state->blend_dst)); GL_CALL(glGetIntegerv(GL_BLEND_EQUATION, &state->blend_equation)); + + /* Depth state */ + GL_CALL(glGetBooleanv(GL_DEPTH_TEST, &state->depth_test_enabled)); + GL_CALL(glGetBooleanv(GL_DEPTH_WRITEMASK, &state->depth_mask)); + GL_CALL(glGetIntegerv(GL_DEPTH_FUNC, &state->depth_func)); + + /* Face culling state */ + GL_CALL(glGetBooleanv(GL_CULL_FACE, &state->cull_face_enabled)); + GL_CALL(glGetIntegerv(GL_CULL_FACE_MODE, &state->cull_face_mode)); + GL_CALL(glGetIntegerv(GL_FRONT_FACE, &state->front_face)); + + /* Stencil state */ + GL_CALL(glGetBooleanv(GL_STENCIL_TEST, &state->stencil_test_enabled)); + GL_CALL(glGetIntegerv(GL_STENCIL_WRITEMASK, (GLint *)&state->stencil_mask)); + GL_CALL(glGetIntegerv(GL_STENCIL_FUNC, &state->stencil_func)); + GL_CALL(glGetIntegerv(GL_STENCIL_REF, &state->stencil_ref)); + GL_CALL(glGetIntegerv(GL_STENCIL_VALUE_MASK, (GLint *)&state->stencil_value_mask)); + + /* Buffer bindings */ +#ifndef GL_VERTEX_ARRAY_BINDING +#ifdef GL_VERTEX_ARRAY_BINDING_OES +#define GL_VERTEX_ARRAY_BINDING GL_VERTEX_ARRAY_BINDING_OES +#else +#define GL_VERTEX_ARRAY_BINDING 0x85B5 +#endif +#endif + GL_CALL(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, (GLint *)&state->current_vao)); + GL_CALL(glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint *)&state->current_vbo)); + GL_CALL(glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, (GLint *)&state->current_ibo)); + GL_CALL(glGetIntegerv(GL_CURRENT_PROGRAM, (GLint *)&state->current_program)); + + /* Texture state */ + GL_CALL(glGetIntegerv(GL_ACTIVE_TEXTURE, &state->active_texture)); + GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint *)&state->bound_texture_2d)); + + /* Viewport and scissor */ + GL_CALL(glGetIntegerv(GL_VIEWPORT, state->viewport)); + GL_CALL(glGetBooleanv(GL_SCISSOR_TEST, &state->scissor_test_enabled)); + GL_CALL(glGetIntegerv(GL_SCISSOR_BOX, state->scissor_box)); + + /* Clear values */ GL_CALL(glGetFloatv(GL_COLOR_CLEAR_VALUE, state->clear_color)); GL_CALL(glGetFloatv(GL_DEPTH_CLEAR_VALUE, &state->clear_depth)); } static void lv_gltf_view_pop_opengl_state(const lv_opengl_state_t * state) { - GL_CALL(glDisable(GL_CULL_FACE)); + /* Restore blend state */ if(state->blend_enabled) { GL_CALL(glEnable(GL_BLEND)); } @@ -187,7 +229,58 @@ static void lv_gltf_view_pop_opengl_state(const lv_opengl_state_t * state) } GL_CALL(glBlendFunc(state->blend_src, state->blend_dst)); GL_CALL(glBlendEquation(state->blend_equation)); - GL_CALL(glDepthMask(GL_TRUE)); + + /* Restore depth state */ + if(state->depth_test_enabled) { + GL_CALL(glEnable(GL_DEPTH_TEST)); + } + else { + GL_CALL(glDisable(GL_DEPTH_TEST)); + } + GL_CALL(glDepthMask(state->depth_mask)); + GL_CALL(glDepthFunc(state->depth_func)); + + /* Restore face culling state */ + if(state->cull_face_enabled) { + GL_CALL(glEnable(GL_CULL_FACE)); + } + else { + GL_CALL(glDisable(GL_CULL_FACE)); + } + GL_CALL(glCullFace(state->cull_face_mode)); + GL_CALL(glFrontFace(state->front_face)); + + /* Restore stencil state */ + if(state->stencil_test_enabled) { + GL_CALL(glEnable(GL_STENCIL_TEST)); + } + else { + GL_CALL(glDisable(GL_STENCIL_TEST)); + } + GL_CALL(glStencilMask(state->stencil_mask)); + GL_CALL(glStencilFunc(state->stencil_func, state->stencil_ref, state->stencil_value_mask)); + + /* Restore buffer bindings */ + GL_CALL(glBindVertexArray(state->current_vao)); + GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, state->current_vbo)); + GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, state->current_ibo)); + GL_CALL(glUseProgram(state->current_program)); + + /* Restore texture state */ + GL_CALL(glActiveTexture(state->active_texture)); + GL_CALL(glBindTexture(GL_TEXTURE_2D, state->bound_texture_2d)); + + /* Restore viewport and scissor */ + GL_CALL(glViewport(state->viewport[0], state->viewport[1], state->viewport[2], state->viewport[3])); + if(state->scissor_test_enabled) { + GL_CALL(glEnable(GL_SCISSOR_TEST)); + } + else { + GL_CALL(glDisable(GL_SCISSOR_TEST)); + } + GL_CALL(glScissor(state->scissor_box[0], state->scissor_box[1], state->scissor_box[2], state->scissor_box[3])); + + /* Restore clear values */ GL_CALL(glClearColor(state->clear_color[0], state->clear_color[1], state->clear_color[2], state->clear_color[3])); GL_CALL(glClearDepthf(state->clear_depth)); }