diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp index 432f66554..7e4b5e30d 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -10,59 +10,21 @@ #include "core/hle/service/nvdrv/devices/ioctl_serialization.h" #include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h" #include "core/hle/service/nvdrv/nvdrv.h" +#include "video_core/zbc_manager.h" namespace Service::Nvidia::Devices { // ZBC helper functions for GPU clearing operations namespace ZBC { std::optional> GetColor(u32 format, u32 type) { - return ZBCManager::Instance().GetZBCColor(format, type); + return VideoCore::ZBCManager::Instance().GetZBCColor(format, type); } std::optional GetDepth(u32 format, u32 type) { - return ZBCManager::Instance().GetZBCDepth(format, type); + return VideoCore::ZBCManager::Instance().GetZBCDepth(format, type); } } -// ZBCManager implementation -std::optional> ZBCManager::GetZBCColor(u32 format, u32 type) const { - std::scoped_lock lock{zbc_table_mutex}; - const auto key = std::make_pair(format, type); - const auto it = zbc_table.find(key); - if (it != zbc_table.end()) { - return it->second.color_ds; - } - return std::nullopt; -} - -std::optional ZBCManager::GetZBCDepth(u32 format, u32 type) const { - std::scoped_lock lock{zbc_table_mutex}; - const auto key = std::make_pair(format, type); - const auto it = zbc_table.find(key); - if (it != zbc_table.end()) { - return it->second.depth; - } - return std::nullopt; -} - -void ZBCManager::StoreZBCEntry(u32 format, u32 type, const std::array& color_ds, - const std::array& color_l2, u32 depth) { - std::scoped_lock lock{zbc_table_mutex}; - - ZBCEntry entry; - entry.color_ds = color_ds; - entry.color_l2 = color_l2; - entry.depth = depth; - entry.format = format; - entry.type = type; - entry.ref_count = 1; - - const auto key = std::make_pair(format, type); - zbc_table[key] = entry; - - LOG_DEBUG(Service_NVDRV, "Global ZBCManager: Stored entry format=0x{:X}, type=0x{:X}, depth=0x{:X}", - format, type, depth); -} nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system_, EventInterface& events_interface_) : nvdevice{system_}, events_interface{events_interface_} { @@ -143,11 +105,11 @@ void nvhost_ctrl_gpu::OnClose(DeviceFD fd) {} // ZBC table management methods std::optional> nvhost_ctrl_gpu::GetZBCColor(u32 format, u32 type) const { - return ZBCManager::Instance().GetZBCColor(format, type); + return VideoCore::ZBCManager::Instance().GetZBCColor(format, type); } std::optional nvhost_ctrl_gpu::GetZBCDepth(u32 format, u32 type) const { - return ZBCManager::Instance().GetZBCDepth(format, type); + return VideoCore::ZBCManager::Instance().GetZBCDepth(format, type); } void nvhost_ctrl_gpu::StoreZBCEntry(const IoctlZbcSetTable& params) { @@ -166,7 +128,7 @@ void nvhost_ctrl_gpu::StoreZBCEntry(const IoctlZbcSetTable& params) { zbc_table[key] = entry; // Also store in global ZBCManager for GPU access - ZBCManager::Instance().StoreZBCEntry(params.format, params.type, entry.color_ds, entry.color_l2, params.depth); + VideoCore::ZBCManager::Instance().StoreZBCEntry(params.format, params.type, entry.color_ds, entry.color_l2, params.depth); LOG_DEBUG(Service_NVDRV, "Stored ZBC entry: format=0x{:X}, type=0x{:X}, depth=0x{:X}", params.format, params.type, params.depth); @@ -331,8 +293,8 @@ NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) { return NvResult::BadParameter; } - // Validate the type parameter (typically 0 for color, 1 for depth) - if (params.type > 1) { + // Validate the type parameter (0=color, 1=depth, 2=stencil) + if (params.type > 2) { LOG_WARNING(Service_NVDRV, "Invalid ZBC type: 0x{:X}", params.type); return NvResult::BadParameter; } @@ -344,7 +306,7 @@ NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) { LOG_DEBUG(Service_NVDRV, "ZBC color_ds: [0x{:08X}, 0x{:08X}, 0x{:08X}, 0x{:08X}]", params.color_ds[0], params.color_ds[1], params.color_ds[2], params.color_ds[3]); LOG_DEBUG(Service_NVDRV, "ZBC color_l2: [0x{:08X}, 0x{:08X}, 0x{:08X}, 0x{:08X}]", - params.color_ds[0], params.color_ds[1], params.color_ds[2], params.color_ds[3]); + params.color_l2[0], params.color_l2[1], params.color_l2[2], params.color_l2[3]); return NvResult::Success; } diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 6a4d7be27..d6e48960c 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -33,6 +33,8 @@ add_library(video_core STATIC delayed_destruction_ring.h dirty_flags.cpp dirty_flags.h + zbc_manager.cpp + zbc_manager.h dma_pusher.cpp dma_pusher.h engines/sw_blitter/blitter.cpp @@ -152,6 +154,8 @@ add_library(video_core STATIC renderer_opengl/gl_graphics_pipeline.h renderer_opengl/gl_rasterizer.cpp renderer_opengl/gl_rasterizer.h + renderer_opengl/gl_zbc_clear.cpp + renderer_opengl/gl_zbc_clear.h renderer_opengl/gl_resource_manager.cpp renderer_opengl/gl_resource_manager.h renderer_opengl/gl_shader_cache.cpp @@ -197,6 +201,8 @@ add_library(video_core STATIC renderer_vulkan/present/window_adapt_pass.h renderer_vulkan/blit_image.cpp renderer_vulkan/blit_image.h + renderer_vulkan/vk_zbc_clear.cpp + renderer_vulkan/vk_zbc_clear.h renderer_vulkan/fixed_pipeline_state.cpp renderer_vulkan/fixed_pipeline_state.h renderer_vulkan/maxwell_to_vk.cpp diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index d376d86d8..45cc31fd7 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -23,6 +23,7 @@ #include "video_core/renderer_opengl/gl_device.h" #include "video_core/renderer_opengl/gl_query_cache.h" #include "video_core/renderer_opengl/gl_rasterizer.h" +#include "video_core/renderer_opengl/gl_zbc_clear.h" #include "video_core/renderer_opengl/gl_shader_cache.h" #include "video_core/renderer_opengl/gl_staging_buffer_pool.h" #include "video_core/renderer_opengl/gl_texture_cache.h" @@ -213,15 +214,50 @@ void RasterizerOpenGL::Clear(u32 layer_count) { } UNIMPLEMENTED_IF(regs.clear_control.use_viewport_clip0); + // Try to use ZBC (Zero Bandwidth Clear) for efficient clearing + bool zbc_used = false; + if (use_color) { - glClearBufferfv(GL_COLOR, regs.clear_surface.RT, regs.clear_color.data()); + // Try ZBC clear first, fall back to regular clear if not available + const u32 rt_index = regs.clear_surface.RT; + const u32 format = static_cast(regs.rt[rt_index].format); + const u32 type = 0; // Color clear type + if (!OpenGL::ZBCClear::ClearColor(format, type, rt_index)) { + glClearBufferfv(GL_COLOR, rt_index, regs.clear_color.data()); + } else { + zbc_used = true; + } } + if (use_depth && use_stencil) { - glClearBufferfi(GL_DEPTH_STENCIL, 0, regs.clear_depth, regs.clear_stencil); + const u32 format = static_cast(regs.zeta.format); + const u32 type = 1; // Depth clear type + if (!OpenGL::ZBCClear::ClearDepthStencil(format, type, regs.clear_stencil)) { + glClearBufferfi(GL_DEPTH_STENCIL, 0, regs.clear_depth, regs.clear_stencil); + } else { + zbc_used = true; + } } else if (use_depth) { - glClearBufferfv(GL_DEPTH, 0, ®s.clear_depth); + const u32 format = static_cast(regs.zeta.format); + const u32 type = 1; // Depth clear type + if (!OpenGL::ZBCClear::ClearDepth(format, type)) { + glClearBufferfv(GL_DEPTH, 0, ®s.clear_depth); + } else { + zbc_used = true; + } } else if (use_stencil) { - glClearBufferiv(GL_STENCIL, 0, ®s.clear_stencil); + // Try ZBC stencil clear first, fall back to regular clear if not available + const u32 format = static_cast(regs.zeta.format); + const u32 type = 2; // Stencil clear type + if (!OpenGL::ZBCClear::ClearStencil(format, type, regs.clear_stencil)) { + glClearBufferiv(GL_STENCIL, 0, ®s.clear_stencil); + } else { + zbc_used = true; + } + } + + if (zbc_used) { + LOG_TRACE(Render_OpenGL, "ZBC: Used ZBC clear for efficient buffer clearing"); } ++num_queued_commands; } @@ -1101,7 +1137,7 @@ void RasterizerOpenGL::SyncColorMask() { flags[Dirty::ColorMask0] = false; auto& mask = regs.color_mask[0]; - glColorMask(mask.R != 0, mask.B != 0, mask.G != 0, mask.A != 0); + glColorMask(mask.R != 0, mask.G != 0, mask.B != 0, mask.A != 0); return; } diff --git a/src/video_core/renderer_opengl/gl_zbc_clear.cpp b/src/video_core/renderer_opengl/gl_zbc_clear.cpp new file mode 100644 index 000000000..eb91e05b1 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_zbc_clear.cpp @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "video_core/renderer_opengl/gl_zbc_clear.h" +#include "common/logging/log.h" +#include "video_core/zbc_manager.h" + +#include + +namespace OpenGL { + +bool ZBCClear::ClearColor(u32 format, u32 type, u32 rt) { + const auto zbc_color = VideoCore::ZBCManager::Instance().GetZBCColor(format, type); + if (!zbc_color) { + LOG_TRACE(Render_OpenGL, "ZBC: No color entry found for format=0x{:X}, type=0x{:X}, using fallback", format, type); + return false; + } + + const auto clear_color = ConvertColorToOpenGL(*zbc_color); + + LOG_TRACE(Render_OpenGL, "ZBC: Using ZBC clear color for format=0x{:X}, type=0x{:X}, rt={}", format, type, rt); + LOG_TRACE(Render_OpenGL, "ZBC: Clear color=[{:.3f}, {:.3f}, {:.3f}, {:.3f}]", + clear_color[0], clear_color[1], clear_color[2], clear_color[3]); + + glClearBufferfv(GL_COLOR, rt, clear_color.data()); + return true; +} + +bool ZBCClear::ClearDepth(u32 format, u32 type) { + const auto zbc_depth = VideoCore::ZBCManager::Instance().GetZBCDepth(format, type); + if (!zbc_depth) { + LOG_TRACE(Render_OpenGL, "ZBC: No depth entry found for format=0x{:X}, type=0x{:X}, using fallback", format, type); + return false; + } + + const f32 clear_depth = VideoCore::ZBCManager::ConvertDepthToFloat(*zbc_depth); + + LOG_TRACE(Render_OpenGL, "ZBC: Using ZBC clear depth for format=0x{:X}, type=0x{:X}", format, type); + LOG_TRACE(Render_OpenGL, "ZBC: Clear depth={:.6f}", clear_depth); + + glClearBufferfv(GL_DEPTH, 0, &clear_depth); + return true; +} + +bool ZBCClear::ClearDepthStencil(u32 format, u32 type, u32 stencil_value) { + const auto zbc_depth = VideoCore::ZBCManager::Instance().GetZBCDepth(format, type); + if (!zbc_depth) { + LOG_TRACE(Render_OpenGL, "ZBC: No depth entry found for format=0x{:X}, type=0x{:X}, using fallback", format, type); + return false; + } + + const f32 clear_depth = VideoCore::ZBCManager::ConvertDepthToFloat(*zbc_depth); + + LOG_TRACE(Render_OpenGL, "ZBC: Using ZBC clear depth-stencil for format=0x{:X}, type=0x{:X}", format, type); + LOG_TRACE(Render_OpenGL, "ZBC: Clear depth={:.6f}, stencil={}", clear_depth, stencil_value); + + glClearBufferfi(GL_DEPTH_STENCIL, 0, clear_depth, static_cast(stencil_value)); + return true; +} + +std::optional> ZBCClear::GetZBCClearColor(u32 format, u32 type) { + const auto zbc_color = VideoCore::ZBCManager::Instance().GetZBCColor(format, type); + if (!zbc_color) { + return std::nullopt; + } + return ConvertColorToOpenGL(*zbc_color); +} + +std::optional ZBCClear::GetZBCClearDepth(u32 format, u32 type) { + const auto zbc_depth = VideoCore::ZBCManager::Instance().GetZBCDepth(format, type); + if (!zbc_depth) { + return std::nullopt; + } + return VideoCore::ZBCManager::ConvertDepthToFloat(*zbc_depth); +} + +std::array ZBCClear::ConvertColorToOpenGL(const std::array& color_u32) { + // Convert ZBC color values to OpenGL format + // The ZBC color values are typically in RGBA8888 format + std::array color_f32; + + // For now, we'll use the first color value and convert it to RGBA + // This might need adjustment based on the actual format used by the game + const u32 primary_color = color_u32[0]; + + // Extract RGBA components (assuming RGBA8888 format) + const u8 r = (primary_color >> 0) & 0xFF; + const u8 g = (primary_color >> 8) & 0xFF; + const u8 b = (primary_color >> 16) & 0xFF; + const u8 a = (primary_color >> 24) & 0xFF; + + // Convert to normalized float values [0.0, 1.0] + color_f32[0] = static_cast(r) / 255.0f; // Red + color_f32[1] = static_cast(g) / 255.0f; // Green + color_f32[2] = static_cast(b) / 255.0f; // Blue + color_f32[3] = static_cast(a) / 255.0f; // Alpha + + // If the color is all zeros, use a default clear color + if (color_f32[0] == 0.0f && color_f32[1] == 0.0f && + color_f32[2] == 0.0f && color_f32[3] == 0.0f) { + // Use a default clear color (black with full alpha) + color_f32 = {0.0f, 0.0f, 0.0f, 1.0f}; + } + + return color_f32; +} + +bool ZBCClear::ClearStencil(u32 format, u32 type, u32 stencil_value) { + // For stencil clearing, we need to check if we have a ZBC entry for stencil type (2) + const auto zbc_color = VideoCore::ZBCManager::Instance().GetZBCColor(format, 2); + if (!zbc_color) { + LOG_TRACE(Render_OpenGL, "ZBC: No stencil entry found for format=0x{:X}, type=0x{:X}, using fallback", format, type); + return false; + } + + // Convert ZBC color to stencil value + // For stencil, we typically use the first component as the stencil value + const u32 zbc_stencil = zbc_color.value()[0]; + + // Convert to the appropriate stencil format + const u32 clear_stencil = zbc_stencil & 0xFF; // Use lower 8 bits for stencil + + LOG_TRACE(Render_OpenGL, "ZBC: Using stencil clear value 0x{:X} for format=0x{:X}", clear_stencil, format); + + // Perform the stencil clear + glClearBufferiv(GL_STENCIL, 0, reinterpret_cast(&clear_stencil)); + + return true; +} + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_zbc_clear.h b/src/video_core/renderer_opengl/gl_zbc_clear.h new file mode 100644 index 000000000..e34b68bc8 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_zbc_clear.h @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "common/common_types.h" +#include "video_core/zbc_manager.h" + +namespace OpenGL { + +/** + * ZBC-aware buffer clearing for OpenGL renderer + * + * This class provides efficient buffer clearing operations using ZBC (Zero Bandwidth Clear) + * table entries when available, falling back to standard clear operations when needed. + */ +class ZBCClear { +public: + /** + * Clear color buffer using ZBC if available + * @param format Surface format identifier + * @param type Clear type (0=color, 1=depth) + * @param rt Render target index + * @return True if ZBC clear was used, false if fallback is needed + */ + static bool ClearColor(u32 format, u32 type, u32 rt); + + /** + * Clear depth buffer using ZBC if available + * @param format Surface format identifier + * @param type Clear type (0=color, 1=depth) + * @return True if ZBC clear was used, false if fallback is needed + */ + static bool ClearDepth(u32 format, u32 type); + + /** + * Clear depth-stencil buffer using ZBC if available + * @param format Surface format identifier + * @param type Clear type (0=color, 1=depth, 2=stencil) + * @param stencil_value Stencil clear value + * @return True if ZBC clear was used, false if fallback is needed + */ + static bool ClearDepthStencil(u32 format, u32 type, u32 stencil_value); + + /** + * Clear stencil buffer using ZBC if available + * @param format Surface format identifier + * @param type Clear type (0=color, 1=depth, 2=stencil) + * @param stencil_value Stencil clear value + * @return True if ZBC clear was used, false if fallback is needed + */ + static bool ClearStencil(u32 format, u32 type, u32 stencil_value); + + /** + * Get ZBC clear color for a specific format and type + * @param format Surface format identifier + * @param type Clear type (0=color, 1=depth) + * @return Optional array of 4 float color values, or nullopt if not available + */ + static std::optional> GetZBCClearColor(u32 format, u32 type); + + /** + * Get ZBC clear depth for a specific format and type + * @param format Surface format identifier + * @param type Clear type (0=color, 1=depth) + * @return Optional depth value, or nullopt if not available + */ + static std::optional GetZBCClearDepth(u32 format, u32 type); + +private: + /** + * Convert ZBC color values to OpenGL format + * @param color_u32 Array of 4 uint32 color values + * @return Array of 4 float color values for OpenGL + */ + static std::array ConvertColorToOpenGL(const std::array& color_u32); +}; + +} // namespace OpenGL diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp index c3db09424..a1f3a0939 100644 --- a/src/video_core/renderer_vulkan/blit_image.cpp +++ b/src/video_core/renderer_vulkan/blit_image.cpp @@ -4,6 +4,7 @@ #include #include "video_core/renderer_vulkan/vk_texture_cache.h" +#include "video_core/renderer_vulkan/vk_zbc_clear.h" #include "common/settings.h" #include "video_core/host_shaders/blit_color_float_frag_spv.h" @@ -592,6 +593,10 @@ void BlitImageHelper::ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, void BlitImageHelper::ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask, const std::array& clear_color, const Region2D& dst_region) { + // Try to use ZBC (Zero Bandwidth Clear) for efficient clearing + // Note: We need the format and type from the context, but for now we'll use fallback + // TODO: Integrate with proper format/type detection from the rendering context + const BlitImagePipelineKey key{ .renderpass = dst_framebuffer->RenderPass(), .operation = Tegra::Engines::Fermi2D::Operation::BlendPremult, diff --git a/src/video_core/renderer_vulkan/present/layer.cpp b/src/video_core/renderer_vulkan/present/layer.cpp index 425eba566..24480ef2b 100644 --- a/src/video_core/renderer_vulkan/present/layer.cpp +++ b/src/video_core/renderer_vulkan/present/layer.cpp @@ -35,7 +35,7 @@ VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) { switch (framebuffer.pixel_format) { case Service::android::PixelFormat::Rgba8888: case Service::android::PixelFormat::Rgbx8888: - return VK_FORMAT_A8B8G8R8_UNORM_PACK32; + return VK_FORMAT_R8G8B8A8_UNORM; case Service::android::PixelFormat::Rgb565: return VK_FORMAT_R5G6B5_UNORM_PACK16; case Service::android::PixelFormat::Bgra8888: @@ -43,7 +43,7 @@ VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) { default: UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}", static_cast(framebuffer.pixel_format)); - return VK_FORMAT_A8B8G8R8_UNORM_PACK32; + return VK_FORMAT_R8G8B8A8_UNORM; } } diff --git a/src/video_core/renderer_vulkan/vk_zbc_clear.cpp b/src/video_core/renderer_vulkan/vk_zbc_clear.cpp new file mode 100644 index 000000000..c1fbafb34 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_zbc_clear.cpp @@ -0,0 +1,172 @@ +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "video_core/renderer_vulkan/vk_zbc_clear.h" +#include "common/logging/log.h" +#include "video_core/zbc_manager.h" + +namespace Vulkan { + +bool ZBCClear::ClearColorImage(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayout layout, + u32 format, u32 type) { + const auto zbc_color = VideoCore::ZBCManager::Instance().GetZBCColor(format, type); + if (!zbc_color) { + LOG_TRACE(Render_Vulkan, "ZBC: No color entry found for format=0x{:X}, type=0x{:X}, using fallback", format, type); + return false; + } + + const VkClearColorValue clear_color = ConvertColorToVulkan(*zbc_color); + + LOG_TRACE(Render_Vulkan, "ZBC: Using ZBC clear color for format=0x{:X}, type=0x{:X}", format, type); + LOG_TRACE(Render_Vulkan, "ZBC: Clear color=[{:.3f}, {:.3f}, {:.3f}, {:.3f}]", + clear_color.float32[0], clear_color.float32[1], clear_color.float32[2], clear_color.float32[3]); + + static constexpr std::array subresources{{{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }}}; + + cmdbuf.ClearColorImage(image, layout, clear_color, subresources); + return true; +} + +bool ZBCClear::ClearDepthStencilImage(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayout layout, + u32 format, u32 type, u32 stencil_value) { + const auto zbc_depth = VideoCore::ZBCManager::Instance().GetZBCDepth(format, type); + if (!zbc_depth) { + LOG_TRACE(Render_Vulkan, "ZBC: No depth entry found for format=0x{:X}, type=0x{:X}, using fallback", format, type); + return false; + } + + const f32 clear_depth = VideoCore::ZBCManager::ConvertDepthToFloat(*zbc_depth); + + LOG_TRACE(Render_Vulkan, "ZBC: Using ZBC clear depth-stencil for format=0x{:X}, type=0x{:X}", format, type); + LOG_TRACE(Render_Vulkan, "ZBC: Clear depth={:.6f}, stencil={}", clear_depth, stencil_value); + + const VkClearDepthStencilValue clear_value{ + .depth = clear_depth, + .stencil = stencil_value, + }; + + + // Use ClearAttachments for depth-stencil clearing + const VkClearValue clear_value_union{ + .depthStencil = clear_value, + }; + + const VkClearAttachment attachment{ + .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, + .colorAttachment = 0, + .clearValue = clear_value_union, + }; + + const VkClearRect clear_rect{ + .rect = {{0, 0}, {0xFFFFFFFF, 0xFFFFFFFF}}, // Clear entire image + .baseArrayLayer = 0, + .layerCount = 1, + }; + + cmdbuf.ClearAttachments({attachment}, {clear_rect}); + return true; +} + +std::optional ZBCClear::GetZBCClearColor(u32 format, u32 type) { + const auto zbc_color = VideoCore::ZBCManager::Instance().GetZBCColor(format, type); + if (!zbc_color) { + return std::nullopt; + } + return ConvertColorToVulkan(*zbc_color); +} + +std::optional ZBCClear::GetZBCClearDepth(u32 format, u32 type) { + const auto zbc_depth = VideoCore::ZBCManager::Instance().GetZBCDepth(format, type); + if (!zbc_depth) { + return std::nullopt; + } + return VideoCore::ZBCManager::ConvertDepthToFloat(*zbc_depth); +} + +VkClearColorValue ZBCClear::ConvertColorToVulkan(const std::array& color_u32) { + // Convert ZBC color values to Vulkan format + // The ZBC color values are typically in RGBA8888 format + VkClearColorValue clear_color{}; + + // For now, we'll use the first color value and convert it to RGBA + // This might need adjustment based on the actual format used by the game + const u32 primary_color = color_u32[0]; + + // Extract RGBA components (assuming RGBA8888 format) + const u8 r = (primary_color >> 0) & 0xFF; + const u8 g = (primary_color >> 8) & 0xFF; + const u8 b = (primary_color >> 16) & 0xFF; + const u8 a = (primary_color >> 24) & 0xFF; + + // Convert to normalized float values [0.0, 1.0] + clear_color.float32[0] = static_cast(r) / 255.0f; // Red + clear_color.float32[1] = static_cast(g) / 255.0f; // Green + clear_color.float32[2] = static_cast(b) / 255.0f; // Blue + clear_color.float32[3] = static_cast(a) / 255.0f; // Alpha + + // If the color is all zeros, use a default clear color + if (clear_color.float32[0] == 0.0f && clear_color.float32[1] == 0.0f && + clear_color.float32[2] == 0.0f && clear_color.float32[3] == 0.0f) { + // Use a default clear color (black with full alpha) + clear_color.float32[0] = 0.0f; // Red + clear_color.float32[1] = 0.0f; // Green + clear_color.float32[2] = 0.0f; // Blue + clear_color.float32[3] = 1.0f; // Alpha + } + + return clear_color; +} + +bool ZBCClear::ClearStencilImage(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayout layout, + u32 format, u32 type, u32 stencil_value) { + // For stencil clearing, we need to check if we have a ZBC entry for stencil type (2) + const auto zbc_color = VideoCore::ZBCManager::Instance().GetZBCColor(format, 2); + if (!zbc_color) { + LOG_TRACE(Render_Vulkan, "ZBC: No stencil entry found for format=0x{:X}, type=0x{:X}, using fallback", format, type); + return false; + } + + // Convert ZBC color to stencil value + // For stencil, we typically use the first component as the stencil value + const u32 zbc_stencil = zbc_color.value()[0]; + + // Convert to the appropriate stencil format + const u32 clear_stencil = zbc_stencil & 0xFF; // Use lower 8 bits for stencil + + LOG_TRACE(Render_Vulkan, "ZBC: Using stencil clear value 0x{:X} for format=0x{:X}", clear_stencil, format); + + // Create clear value for stencil + const VkClearDepthStencilValue clear_value{ + .depth = 1.0f, // Default depth value + .stencil = clear_stencil, + }; + + // Use ClearAttachments for stencil clearing + const VkClearValue clear_value_union{ + .depthStencil = clear_value, + }; + + const VkClearAttachment attachment{ + .aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT, + .colorAttachment = 0, + .clearValue = clear_value_union, + }; + + const VkClearRect clear_rect{ + .rect = {{0, 0}, {0xFFFFFFFF, 0xFFFFFFFF}}, // Clear entire image + .baseArrayLayer = 0, + .layerCount = 1, + }; + + cmdbuf.ClearAttachments({attachment}, {clear_rect}); + + return true; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_zbc_clear.h b/src/video_core/renderer_vulkan/vk_zbc_clear.h new file mode 100644 index 000000000..dc20a23dd --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_zbc_clear.h @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "common/common_types.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" +#include "video_core/zbc_manager.h" + +namespace Vulkan { + +/** + * ZBC-aware buffer clearing for Vulkan renderer + * + * This class provides efficient buffer clearing operations using ZBC (Zero Bandwidth Clear) + * table entries when available, falling back to standard clear operations when needed. + */ +class ZBCClear { +public: + /** + * Clear color image using ZBC if available + * @param cmdbuf Vulkan command buffer + * @param image Image to clear + * @param layout Current image layout + * @param format Surface format identifier + * @param type Clear type (0=color, 1=depth) + * @return True if ZBC clear was used, false if fallback is needed + */ + static bool ClearColorImage(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayout layout, + u32 format, u32 type); + + /** + * Clear depth-stencil image using ZBC if available + * @param cmdbuf Vulkan command buffer + * @param image Image to clear + * @param layout Current image layout + * @param format Surface format identifier + * @param type Clear type (0=color, 1=depth, 2=stencil) + * @param stencil_value Stencil clear value + * @return True if ZBC clear was used, false if fallback is needed + */ + static bool ClearDepthStencilImage(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayout layout, + u32 format, u32 type, u32 stencil_value); + + /** + * Clear stencil image using ZBC if available + * @param cmdbuf Vulkan command buffer + * @param image Image to clear + * @param layout Current image layout + * @param format Surface format identifier + * @param type Clear type (0=color, 1=depth, 2=stencil) + * @param stencil_value Stencil clear value + * @return True if ZBC clear was used, false if fallback is needed + */ + static bool ClearStencilImage(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayout layout, + u32 format, u32 type, u32 stencil_value); + + /** + * Get ZBC clear color for a specific format and type + * @param format Surface format identifier + * @param type Clear type (0=color, 1=depth) + * @return Optional VkClearColorValue, or nullopt if not available + */ + static std::optional GetZBCClearColor(u32 format, u32 type); + + /** + * Get ZBC clear depth for a specific format and type + * @param format Surface format identifier + * @param type Clear type (0=color, 1=depth) + * @return Optional depth value, or nullopt if not available + */ + static std::optional GetZBCClearDepth(u32 format, u32 type); + +private: + /** + * Convert ZBC color values to Vulkan format + * @param color_u32 Array of 4 uint32 color values + * @return VkClearColorValue for Vulkan + */ + static VkClearColorValue ConvertColorToVulkan(const std::array& color_u32); +}; + +} // namespace Vulkan diff --git a/src/video_core/zbc_manager.cpp b/src/video_core/zbc_manager.cpp new file mode 100644 index 000000000..7c2b90e1a --- /dev/null +++ b/src/video_core/zbc_manager.cpp @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "video_core/zbc_manager.h" +#include "common/logging/log.h" + +namespace VideoCore { + +std::optional> ZBCManager::GetZBCColor(u32 format, u32 type) const { + std::scoped_lock lock{zbc_table_mutex}; + const auto key = std::make_pair(format, type); + const auto it = zbc_table.find(key); + if (it != zbc_table.end()) { + LOG_TRACE(Service_NVDRV, "ZBC: Retrieved color for format=0x{:X}, type=0x{:X}", format, type); + return it->second.color_ds; + } + LOG_TRACE(Service_NVDRV, "ZBC: No color entry found for format=0x{:X}, type=0x{:X}", format, type); + return std::nullopt; +} + +std::optional ZBCManager::GetZBCDepth(u32 format, u32 type) const { + std::scoped_lock lock{zbc_table_mutex}; + const auto key = std::make_pair(format, type); + const auto it = zbc_table.find(key); + if (it != zbc_table.end()) { + LOG_TRACE(Service_NVDRV, "ZBC: Retrieved depth for format=0x{:X}, type=0x{:X}", format, type); + return it->second.depth; + } + LOG_TRACE(Service_NVDRV, "ZBC: No depth entry found for format=0x{:X}, type=0x{:X}", format, type); + return std::nullopt; +} + +void ZBCManager::StoreZBCEntry(u32 format, u32 type, const std::array& color_ds, + const std::array& color_l2, u32 depth) { + std::scoped_lock lock{zbc_table_mutex}; + + ZBCEntry entry; + entry.color_ds = color_ds; + entry.color_l2 = color_l2; + entry.depth = depth; + entry.format = format; + entry.type = type; + entry.ref_count = 1; + + const auto key = std::make_pair(format, type); + zbc_table[key] = entry; + + LOG_DEBUG(Service_NVDRV, "ZBC: Stored entry format=0x{:X}, type=0x{:X}, depth=0x{:X}", + format, type, depth); + LOG_TRACE(Service_NVDRV, "ZBC: color_ds=[0x{:08X}, 0x{:08X}, 0x{:08X}, 0x{:08X}]", + color_ds[0], color_ds[1], color_ds[2], color_ds[3]); + LOG_TRACE(Service_NVDRV, "ZBC: color_l2=[0x{:08X}, 0x{:08X}, 0x{:08X}, 0x{:08X}]", + color_l2[0], color_l2[1], color_l2[2], color_l2[3]); +} + +std::array ZBCManager::ConvertToFloat(const std::array& color_u32) { + std::array color_f32; + + // Convert from packed RGBA format to normalized float values + // Assuming the color values are in RGBA8888 format + for (size_t i = 0; i < 4; ++i) { + const u32 packed = color_u32[i]; + + // Extract RGBA components (assuming RGBA8888 format) + const u8 r = (packed >> 0) & 0xFF; + const u8 g = (packed >> 8) & 0xFF; + const u8 b = (packed >> 16) & 0xFF; + const u8 a = (packed >> 24) & 0xFF; + + // Convert to normalized float values [0.0, 1.0] + color_f32[0] = static_cast(r) / 255.0f; // Red + color_f32[1] = static_cast(g) / 255.0f; // Green + color_f32[2] = static_cast(b) / 255.0f; // Blue + color_f32[3] = static_cast(a) / 255.0f; // Alpha + + // For now, we'll use the first component as the primary color + // This might need adjustment based on the actual format used + break; + } + + // If we only have one component, replicate it for RGBA + if (color_f32[0] == 0.0f && color_f32[1] == 0.0f && + color_f32[2] == 0.0f && color_f32[3] == 0.0f) { + // Fallback: use the first uint32 as a single color value + const u32 first_color = color_u32[0]; + const f32 normalized = static_cast(first_color) / static_cast(0xFFFFFFFF); + color_f32 = {normalized, normalized, normalized, 1.0f}; + } + + return color_f32; +} + +f32 ZBCManager::ConvertDepthToFloat(u32 depth_u32) { + // Convert depth value to normalized float [0.0, 1.0] + // This assumes 32-bit depth format + return static_cast(depth_u32) / static_cast(0xFFFFFFFF); +} + +bool ZBCManager::HasZBCEntry(u32 format, u32 type) const { + std::scoped_lock lock{zbc_table_mutex}; + const auto key = std::make_pair(format, type); + return zbc_table.find(key) != zbc_table.end(); +} + +bool ZBCManager::RemoveZBCEntry(u32 format, u32 type) { + std::scoped_lock lock{zbc_table_mutex}; + const auto key = std::make_pair(format, type); + const auto it = zbc_table.find(key); + if (it != zbc_table.end()) { + zbc_table.erase(it); + LOG_DEBUG(Service_NVDRV, "ZBC: Removed entry format=0x{:X}, type=0x{:X}", format, type); + return true; + } + return false; +} + +void ZBCManager::ClearAllEntries() { + std::scoped_lock lock{zbc_table_mutex}; + const size_t count = zbc_table.size(); + zbc_table.clear(); + LOG_DEBUG(Service_NVDRV, "ZBC: Cleared all {} entries", count); +} + +} // namespace VideoCore diff --git a/src/video_core/zbc_manager.h b/src/video_core/zbc_manager.h new file mode 100644 index 000000000..b2187d906 --- /dev/null +++ b/src/video_core/zbc_manager.h @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "common/common_types.h" + +namespace VideoCore { + +/** + * ZBC (Zero Bandwidth Clear) Manager for efficient GPU buffer clearing operations + * + * This class manages ZBC table entries that store pre-defined clear colors and depth values + * for efficient buffer clearing operations. It integrates with the GPU rendering pipeline + * to provide optimized clear operations without requiring bandwidth for clear values. + */ +class ZBCManager { +public: + static ZBCManager& Instance() { + static ZBCManager instance; + return instance; + } + + // ZBC table entry structure + struct ZBCEntry { + std::array color_ds; // Direct Surface color values + std::array color_l2; // L2 cache color values + u32 depth; // Depth clear value + u32 format; // Surface format + u32 type; // Clear type (0=color, 1=depth) + u32 ref_count; // Reference count for cleanup + }; + + /** + * Get ZBC color values for a specific format and type + * @param format Surface format identifier + * @param type Clear type (0=color, 1=depth) + * @return Optional array of 4 color values, or nullopt if not found + */ + std::optional> GetZBCColor(u32 format, u32 type) const; + + /** + * Get ZBC depth value for a specific format and type + * @param format Surface format identifier + * @param type Clear type (0=color, 1=depth) + * @return Optional depth value, or nullopt if not found + */ + std::optional GetZBCDepth(u32 format, u32 type) const; + + /** + * Store ZBC entry in the table + * @param format Surface format identifier + * @param type Clear type (0=color, 1=depth) + * @param color_ds Direct Surface color values + * @param color_l2 L2 cache color values + * @param depth Depth clear value + */ + void StoreZBCEntry(u32 format, u32 type, const std::array& color_ds, + const std::array& color_l2, u32 depth); + + /** + * Convert ZBC color values to floating point for OpenGL/Vulkan + * @param color_u32 Array of 4 uint32 color values + * @return Array of 4 float color values normalized to [0,1] + */ + static std::array ConvertToFloat(const std::array& color_u32); + + /** + * Convert ZBC depth value to floating point + * @param depth_u32 Depth value as uint32 + * @return Depth value as float normalized to [0,1] + */ + static f32 ConvertDepthToFloat(u32 depth_u32); + + /** + * Check if ZBC entry exists for given format and type + * @param format Surface format identifier + * @param type Clear type (0=color, 1=depth) + * @return True if entry exists, false otherwise + */ + bool HasZBCEntry(u32 format, u32 type) const; + + /** + * Remove ZBC entry from table + * @param format Surface format identifier + * @param type Clear type (0=color, 1=depth) + * @return True if entry was removed, false if not found + */ + bool RemoveZBCEntry(u32 format, u32 type); + + /** + * Clear all ZBC entries + */ + void ClearAllEntries(); + +private: + ZBCManager() = default; + ~ZBCManager() = default; + ZBCManager(const ZBCManager&) = delete; + ZBCManager& operator=(const ZBCManager&) = delete; + + mutable std::mutex zbc_table_mutex; + std::map, ZBCEntry> zbc_table; // Key: (format, type) +}; + +} // namespace VideoCore