From d93adebb16a1137e3dc0a5afb627ef652ff6b569 Mon Sep 17 00:00:00 2001 From: Wunk Date: Wed, 1 Jul 2026 08:25:03 -0700 Subject: [PATCH] renderer_gl: Fix OpenGL texture unit state tracking (#2242) * video_core: Implement texture-layer clears Allow specific texture-layers to be targetted for clears rather than just defaulting to the first layer, or clearing all layers after in the case of Vulkan. Allows specific faces of a cubemap to be cleared. * video_core: Initialize `NULL_SURFACE_CUBE_ID` Properly initialize the `NULL_SURFACE_CUBE_ID` texture to ensure that all 6 of its faces are black-transparent. * renderer_gl: Add explicit texture-unit target state tracking The `.target` field can be either a `GL_TEXTURE_2D` or a `GL_TEXTURE_CUBE_MAP` and must be explicitly set when assigning the `.texture_2d` or there is a risk of leaking a previous texture state into the current one in the case that a texture-cube was binded into slot 0 and it becomes a texture-2d sometime after. --- .../rasterizer_cache/rasterizer_cache.h | 16 ++++++++++++++++ src/video_core/rasterizer_cache/utils.h | 3 ++- .../renderer_opengl/gl_blit_helper.cpp | 13 +++++++++++++ src/video_core/renderer_opengl/gl_rasterizer.cpp | 1 + .../renderer_opengl/gl_texture_runtime.cpp | 9 ++++++--- .../renderer_vulkan/vk_texture_runtime.cpp | 7 ++++--- 6 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/video_core/rasterizer_cache/rasterizer_cache.h b/src/video_core/rasterizer_cache/rasterizer_cache.h index 26156cb56..c25bac021 100644 --- a/src/video_core/rasterizer_cache/rasterizer_cache.h +++ b/src/video_core/rasterizer_cache/rasterizer_cache.h @@ -71,12 +71,28 @@ RasterizerCache::RasterizerCache(Memory::MemorySystem& memory_, auto& null_surface = slot_surfaces[NULL_SURFACE_ID]; runtime.ClearTexture(null_surface, { .texture_level = 0, + .texture_layer = 0, .texture_rect = null_surface.GetScaledRect(), .value = { .color = {0.f, 0.f, 0.f, 0.f}, }, }); + + auto& null_surface_cube = slot_surfaces[NULL_SURFACE_CUBE_ID]; + // Clear all cubemap faces + for (u32 cube_layer_index = 0; cube_layer_index < 6; ++cube_layer_index) { + runtime.ClearTexture(null_surface_cube, + { + .texture_level = 0, + .texture_layer = cube_layer_index, + .texture_rect = null_surface_cube.GetScaledRect(), + .value = + { + .color = {0.f, 0.f, 0.f, 0.f}, + }, + }); + } } template diff --git a/src/video_core/rasterizer_cache/utils.h b/src/video_core/rasterizer_cache/utils.h index 3971bc4be..b6bcbec82 100644 --- a/src/video_core/rasterizer_cache/utils.h +++ b/src/video_core/rasterizer_cache/utils.h @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -30,6 +30,7 @@ union ClearValue { struct TextureClear { u32 texture_level; + u32 texture_layer; Common::Rectangle texture_rect; ClearValue value; }; diff --git a/src/video_core/renderer_opengl/gl_blit_helper.cpp b/src/video_core/renderer_opengl/gl_blit_helper.cpp index 30a502316..625bd20f0 100644 --- a/src/video_core/renderer_opengl/gl_blit_helper.cpp +++ b/src/video_core/renderer_opengl/gl_blit_helper.cpp @@ -88,6 +88,7 @@ bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest, SCOPE_EXIT({ prev_state.Apply(); }); state.texture_units[0].texture_2d = source.Handle(); + state.texture_units[0].target = GL_TEXTURE_2D; state.texture_units[0].sampler = 0; state.texture_units[1].sampler = 0; @@ -103,6 +104,7 @@ bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest, temp_tex.Release(); temp_tex.Create(); state.texture_units[1].texture_2d = temp_tex.handle; + state.texture_units[1].target = GL_TEXTURE_2D; state.Apply(); glActiveTexture(GL_TEXTURE1); glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH24_STENCIL8, temp_extent.width, @@ -111,6 +113,7 @@ bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest, glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } state.texture_units[1].texture_2d = temp_tex.handle; + state.texture_units[1].target = GL_TEXTURE_2D; state.Apply(); glActiveTexture(GL_TEXTURE1); @@ -145,6 +148,7 @@ bool BlitHelper::ConvertRGBA4ToRGB5A1(Surface& source, Surface& dest, SCOPE_EXIT({ prev_state.Apply(); }); state.texture_units[0].texture_2d = source.Handle(); + state.texture_units[0].target = GL_TEXTURE_2D; const Common::Rectangle src_rect{copy.src_offset.x, copy.src_offset.y + copy.extent.height, copy.src_offset.x + copy.extent.width, copy.src_offset.x}; @@ -206,6 +210,7 @@ void BlitHelper::FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& b texture.fbo.Create(); texture.tex.Create(); state.texture_units[1].texture_2d = texture.tex.handle; + state.texture_units[1].target = GL_TEXTURE_2D; state.draw.draw_framebuffer = texture.fbo.handle; state.Apply(); glActiveTexture(GL_TEXTURE1); @@ -228,6 +233,10 @@ void BlitHelper::FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& b state.texture_units[1].texture_2d = LUMAD.tex.handle; state.texture_units[2].texture_2d = XY.tex.handle; + state.texture_units[0].target = GL_TEXTURE_2D; + state.texture_units[1].target = GL_TEXTURE_2D; + state.texture_units[2].target = GL_TEXTURE_2D; + // gradient x pass Draw(gradient_x_program, XY.tex.handle, XY.fbo.handle, 0, temp_rect); @@ -249,6 +258,7 @@ void BlitHelper::FilterBicubic(Surface& surface, const VideoCore::TextureBlit& b const OpenGLState prev_state = OpenGLState::GetCurState(); SCOPE_EXIT({ prev_state.Apply(); }); state.texture_units[0].texture_2d = surface.Handle(0); + state.texture_units[0].target = GL_TEXTURE_2D; SetParams(bicubic_program, surface.RealExtent(false), blit.src_rect); Draw(bicubic_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_rect); } @@ -257,6 +267,7 @@ void BlitHelper::FilterScaleForce(Surface& surface, const VideoCore::TextureBlit const OpenGLState prev_state = OpenGLState::GetCurState(); SCOPE_EXIT({ prev_state.Apply(); }); state.texture_units[0].texture_2d = surface.Handle(0); + state.texture_units[0].target = GL_TEXTURE_2D; SetParams(scale_force_program, surface.RealExtent(false), blit.src_rect); Draw(scale_force_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_rect); } @@ -265,6 +276,7 @@ void BlitHelper::FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit const OpenGLState prev_state = OpenGLState::GetCurState(); SCOPE_EXIT({ prev_state.Apply(); }); state.texture_units[0].texture_2d = surface.Handle(0); + state.texture_units[0].target = GL_TEXTURE_2D; glProgramUniform1f(xbrz_program.handle, 2, static_cast(surface.res_scale)); SetParams(xbrz_program, surface.RealExtent(false), blit.src_rect); Draw(xbrz_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_rect); @@ -274,6 +286,7 @@ void BlitHelper::FilterMMPX(Surface& surface, const VideoCore::TextureBlit& blit const OpenGLState prev_state = OpenGLState::GetCurState(); SCOPE_EXIT({ prev_state.Apply(); }); state.texture_units[0].texture_2d = surface.Handle(0); + state.texture_units[0].target = GL_TEXTURE_2D; SetParams(mmpx_program, surface.RealExtent(false), blit.src_rect); Draw(mmpx_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_rect); } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 59ed5bd46..bbfe5cafb 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -717,6 +717,7 @@ void RasterizerOpenGL::SyncTextureUnits(const Framebuffer* framebuffer) { if (!IsFeedbackLoop(texture_index, framebuffer, surface)) { BindMaterial(texture_index, surface); state.texture_units[texture_index].texture_2d = surface.Handle(); + state.texture_units[texture_index].target = GL_TEXTURE_2D; } } diff --git a/src/video_core/renderer_opengl/gl_texture_runtime.cpp b/src/video_core/renderer_opengl/gl_texture_runtime.cpp index b35d8f8fe..bd67cfc0c 100644 --- a/src/video_core/renderer_opengl/gl_texture_runtime.cpp +++ b/src/video_core/renderer_opengl/gl_texture_runtime.cpp @@ -222,8 +222,9 @@ bool TextureRuntime::ClearTextureWithoutFbo(Surface& surface, UNREACHABLE_MSG("Unknown surface type {}", surface.type); } glClearTexSubImage(surface.Handle(), clear.texture_level, clear.texture_rect.left, - clear.texture_rect.bottom, 0, clear.texture_rect.GetWidth(), - clear.texture_rect.GetHeight(), 1, format, type, &clear.value); + clear.texture_rect.bottom, clear.texture_layer, + clear.texture_rect.GetWidth(), clear.texture_rect.GetHeight(), 1, format, + type, &clear.value); return true; } @@ -245,7 +246,7 @@ void TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClea state.draw.draw_framebuffer = draw_fbos[FboIndex(surface.type)].handle; state.Apply(); - surface.Attach(GL_DRAW_FRAMEBUFFER, clear.texture_level, 0); + surface.Attach(GL_DRAW_FRAMEBUFFER, clear.texture_level, clear.texture_layer); switch (surface.type) { case SurfaceType::Color: @@ -326,6 +327,7 @@ void TextureRuntime::GenerateMipmaps(Surface& surface) { const auto generate = [&](u32 index) { state.texture_units[0].texture_2d = surface.Handle(index); + state.texture_units[0].target = GL_TEXTURE_2D; state.Apply(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, surface.levels - 1); glGenerateMipmap(GL_TEXTURE_2D); @@ -554,6 +556,7 @@ bool Surface::DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download, // that only support up to 4.3 OpenGLState state = OpenGLState::GetCurState(); state.texture_units[0].texture_2d = Handle(0); + state.texture_units[0].target = GL_TEXTURE_2D; state.Apply(); glGetTexImage(GL_TEXTURE_2D, download.texture_level, tuple.format, tuple.type, diff --git a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp index c62757384..dad2bf8f8 100644 --- a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp @@ -350,8 +350,8 @@ bool TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClea .aspectMask = params.aspect, .baseMipLevel = clear.texture_level, .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, + .baseArrayLayer = clear.texture_layer, + .layerCount = 1, }; const vk::ImageMemoryBarrier pre_barrier = { @@ -436,7 +436,8 @@ void TextureRuntime::ClearTextureWithRenderpass(Surface& surface, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = params.src_image, - .subresourceRange = MakeSubresourceRange(params.aspect, clear.texture_level), + .subresourceRange = + MakeSubresourceRange(params.aspect, clear.texture_level, 1, clear.texture_layer), }; const vk::ImageMemoryBarrier post_barrier = {