From 32f5cd17b6d2f616ee1a061f9266d8039f25e7de Mon Sep 17 00:00:00 2001 From: Taylor Richards Date: Wed, 23 Jul 2025 13:18:52 -0400 Subject: [PATCH 1/5] only allow tcache flush at mission start/restart --- code/graphics/opengl/gropengltexture.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/code/graphics/opengl/gropengltexture.cpp b/code/graphics/opengl/gropengltexture.cpp index 6407df36a47..cea010efee4 100644 --- a/code/graphics/opengl/gropengltexture.cpp +++ b/code/graphics/opengl/gropengltexture.cpp @@ -1113,11 +1113,6 @@ int gr_opengl_tcache_set_internal(int bitmap_handle, int bitmap_type, float *u_s GR_DEBUG_SCOPE("Activate texture"); - if (GL_last_detail != Detail.hardware_textures) { - GL_last_detail = Detail.hardware_textures; - opengl_tcache_flush(); - } - auto t = bm_get_gr_info(bitmap_handle, true); if (!bm_is_render_target(bitmap_handle) && t->bitmap_handle < 0) @@ -1193,7 +1188,14 @@ void opengl_preload_init() if (gr_screen.mode != GR_OPENGL) return; -// opengl_tcache_flush (); + // If texture detail level has changed since last mission load then flush the + // cache to allow for texture resizing. We should only get here very early + // during mission load (and restart) which should allow for render targets + // to be (re)created normally. + if (GL_last_detail != Detail.hardware_textures) { + GL_last_detail = Detail.hardware_textures; + opengl_tcache_flush(); + } } int gr_opengl_preload(int bitmap_num, int is_aabitmap) From d146d421b5f2e3d55a4702dab65c57bc89980945 Mon Sep 17 00:00:00 2001 From: Taylor Richards Date: Wed, 23 Jul 2025 13:21:18 -0400 Subject: [PATCH 2/5] fix mipmap resizing bug The if statement wouldn't trigger as defined which caused the incorrect data offsets to be used. This resulted in corrupted textures and invalid memory reads. --- code/graphics/opengl/gropengltexture.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/code/graphics/opengl/gropengltexture.cpp b/code/graphics/opengl/gropengltexture.cpp index cea010efee4..cd15e7a1d59 100644 --- a/code/graphics/opengl/gropengltexture.cpp +++ b/code/graphics/opengl/gropengltexture.cpp @@ -673,8 +673,10 @@ static int opengl_texture_set_level(int bitmap_handle, int bitmap_type, int bmap auto mipmap_w = tex_w; auto mipmap_h = tex_h; - // should never have mipmap levels if we also have to manually resize - if ((mipmap_levels > 1) && resize) { + // if we are doing mipmap resizing we need to account for adjusted tex size + // (we can end up with only one mipmap level if base_level is high enough so don't check it) + if (base_level > 0) { + Assertion(resize == false, "ERROR: Cannot use manual and mipmap resizing at the same time!"); Assert(texmem == nullptr); // If we have mipmaps then tex_w/h are already adjusted for the base level but that will cause problems with From 9bf368c6e0b2e27894cda6c0016290173852efc4 Mon Sep 17 00:00:00 2001 From: Taylor Richards Date: Wed, 23 Jul 2025 13:23:18 -0400 Subject: [PATCH 3/5] always set tcache_set outputs to valid values In the case of an error tcache_set does not set it's output variables which can lead to issues with error handling. So set the those variables to sane defaults early on in case of failure. --- code/graphics/opengl/gropengltexture.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/code/graphics/opengl/gropengltexture.cpp b/code/graphics/opengl/gropengltexture.cpp index cd15e7a1d59..6f945b8f1db 100644 --- a/code/graphics/opengl/gropengltexture.cpp +++ b/code/graphics/opengl/gropengltexture.cpp @@ -1163,6 +1163,11 @@ int gr_opengl_tcache_set(int bitmap_handle, int bitmap_type, float *u_scale, flo int rc = 0; + // set output defaults in case of error + *u_scale = 1.0f; + *v_scale = 1.0f; + *array_index = 0; + if (bitmap_handle < 0) { return 0; } From ab7f937e01a41363b20f18925dd52aad73723eb7 Mon Sep 17 00:00:00 2001 From: Taylor Richards Date: Wed, 23 Jul 2025 13:25:45 -0400 Subject: [PATCH 4/5] fix some render targets not getting cleaned up properly --- code/scripting/api/objs/texture.cpp | 4 ++-- code/ship/ship.cpp | 2 +- code/starfield/starfield.cpp | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/code/scripting/api/objs/texture.cpp b/code/scripting/api/objs/texture.cpp index 17b0b6f6711..bc3c3ec117b 100644 --- a/code/scripting/api/objs/texture.cpp +++ b/code/scripting/api/objs/texture.cpp @@ -28,7 +28,7 @@ texture_h::~texture_h() //the textures using load_count. Anything that creates a texture object must also increase load count, unless it is //created in a way that already increases load_count (like bm_load). That way, a texture going out of scope needs to be //released and is safed against memleaks. -Lafiel - bm_release(handle); + bm_release(handle, 1); } bool texture_h::isValid() const { return bm_is_valid(handle) != 0; } texture_h::texture_h(texture_h&& other) noexcept { @@ -114,7 +114,7 @@ ADE_FUNC(unload, l_Texture, NULL, "Unloads a texture from memory", NULL, NULL) if (!th->isValid()) return ADE_RETURN_NIL; - bm_release(th->handle); + bm_release(th->handle, 1); //WMC - invalidate this handle th->handle = -1; diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index addb3f0355e..a74ebb19890 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -8452,7 +8452,7 @@ void ship_close_cockpit_displays(ship* shipp) } if ( Player_displays[i].target >= 0 ) { - bm_release(Player_displays[i].target); + bm_release(Player_displays[i].target, 1); } } diff --git a/code/starfield/starfield.cpp b/code/starfield/starfield.cpp index 0f1a7168639..4d5d5c10dbd 100644 --- a/code/starfield/starfield.cpp +++ b/code/starfield/starfield.cpp @@ -1073,12 +1073,19 @@ void stars_level_close() { } } + if (gr_screen.irrmap_render_target >= 0) { + if ( bm_release(gr_screen.irrmap_render_target, 1) ) { + gr_screen.irrmap_render_target = -1; + } + } + if (Mission_env_map >= 0) { bm_release(Mission_env_map); Mission_env_map = -1; } ENVMAP = Default_env_map; + IRRMAP = -1; } From 63d5a4b4513b5da3242aef8532cc5ea6db845559 Mon Sep 17 00:00:00 2001 From: Taylor Richards Date: Wed, 23 Jul 2025 13:29:05 -0400 Subject: [PATCH 5/5] add check for making sure render target is valid Render targets are invalidated after tcache_flush is called so give the ability to detect that so that the render target can be recreated as required. --- code/graphics/2d.h | 3 +++ code/graphics/grstub.cpp | 6 ++++++ code/graphics/opengl/gropengl.cpp | 1 + code/graphics/opengl/gropenglbmpman.cpp | 15 +++++++++++++++ code/graphics/opengl/gropenglbmpman.h | 1 + code/render/batching.cpp | 8 ++++++-- 6 files changed, 32 insertions(+), 2 deletions(-) diff --git a/code/graphics/2d.h b/code/graphics/2d.h index b701bdeefc6..d6eae7694c6 100644 --- a/code/graphics/2d.h +++ b/code/graphics/2d.h @@ -787,6 +787,7 @@ typedef struct screen { std::function gf_bm_make_render_target; std::function gf_bm_set_render_target; + std::function gf_bm_is_valid_render_target; std::function gf_set_texture_addressing; @@ -1129,6 +1130,8 @@ inline int gr_bm_set_render_target(int n, int face = -1) return gr_screen.gf_bm_set_render_target(n, face); } +#define gr_bm_is_valid_render_target GR_CALL(gr_screen.gf_bm_is_valid_render_target) + #define gr_set_texture_addressing GR_CALL(gr_screen.gf_set_texture_addressing) inline gr_buffer_handle gr_create_buffer(BufferType type, BufferUsageHint usage) diff --git a/code/graphics/grstub.cpp b/code/graphics/grstub.cpp index d3155560e2b..9a737b40054 100644 --- a/code/graphics/grstub.cpp +++ b/code/graphics/grstub.cpp @@ -274,6 +274,11 @@ int gr_stub_bm_set_render_target(int /*n*/, int /*face*/) return 0; } +bool gr_stub_bm_is_valid_render_target(int /*bitmap_handle*/) +{ + return false; +} + void gr_stub_bm_create(bitmap_slot* /*slot*/) { } @@ -542,6 +547,7 @@ void gr_stub_init_function_pointers() { gr_screen.gf_bm_data = gr_stub_bm_data; gr_screen.gf_bm_make_render_target = gr_stub_bm_make_render_target; gr_screen.gf_bm_set_render_target = gr_stub_bm_set_render_target; + gr_screen.gf_bm_is_valid_render_target = gr_stub_bm_is_valid_render_target; gr_screen.gf_set_cull = gr_stub_set_cull; gr_screen.gf_set_color_buffer = gr_stub_set_color_buffer; diff --git a/code/graphics/opengl/gropengl.cpp b/code/graphics/opengl/gropengl.cpp index d13a48bb4ce..1331c9fbdf5 100644 --- a/code/graphics/opengl/gropengl.cpp +++ b/code/graphics/opengl/gropengl.cpp @@ -1002,6 +1002,7 @@ void gr_opengl_init_function_pointers() gr_screen.gf_bm_data = gr_opengl_bm_data; gr_screen.gf_bm_make_render_target = gr_opengl_bm_make_render_target; gr_screen.gf_bm_set_render_target = gr_opengl_bm_set_render_target; + gr_screen.gf_bm_is_valid_render_target = gr_opengl_bm_is_valid_render_target; gr_screen.gf_set_cull = gr_opengl_set_cull; gr_screen.gf_set_color_buffer = gr_opengl_set_color_buffer; diff --git a/code/graphics/opengl/gropenglbmpman.cpp b/code/graphics/opengl/gropenglbmpman.cpp index fa49b068a6e..f022cd2cff6 100644 --- a/code/graphics/opengl/gropenglbmpman.cpp +++ b/code/graphics/opengl/gropenglbmpman.cpp @@ -141,6 +141,21 @@ int gr_opengl_bm_set_render_target(int n, int face) return 0; } +bool gr_opengl_bm_is_valid_render_target(int bitmap_handle) +{ + if (bitmap_handle < 0) { + return false; + } + + if ( !bm_is_render_target(bitmap_handle) ) { + return false; + } + + auto ts = bm_get_gr_info(bitmap_handle); + + return (ts->texture_id && (ts->fbo_id >= 0)); +} + bool gr_opengl_bm_data(int /*n*/, bitmap* /*bm*/) { // Do nothing here diff --git a/code/graphics/opengl/gropenglbmpman.h b/code/graphics/opengl/gropenglbmpman.h index c4eb6300b74..c348b12706b 100644 --- a/code/graphics/opengl/gropenglbmpman.h +++ b/code/graphics/opengl/gropenglbmpman.h @@ -34,5 +34,6 @@ bool gr_opengl_bm_data(int handle, bitmap* bm); void gr_opengl_bm_save_render_target(int slot); int gr_opengl_bm_make_render_target(int n, int *width, int *height, int *bpp, int *mm_lvl, int flags); int gr_opengl_bm_set_render_target(int n, int face); +bool gr_opengl_bm_is_valid_render_target(int bitmap_handle); #endif // _OGL_BMPMAN_H diff --git a/code/render/batching.cpp b/code/render/batching.cpp index 2c5555804f0..801934c22a0 100644 --- a/code/render/batching.cpp +++ b/code/render/batching.cpp @@ -811,9 +811,13 @@ void batching_add_line(vec3d *start, vec3d *end, float widthStart, float widthEn return; } - if (lineTexture < 0) + if ( !gr_bm_is_valid_render_target(lineTexture) ) { - //We only need a single pixel sized texture to render as many lines as we want. + if (lineTexture >= 0) { + bm_release(lineTexture, 1); + } + + //We only need a single pixel sized texture to render as many lines as we want. //If it doesn't exist yet, then we make one! auto previous_target = gr_screen.rendering_to_texture;