feat: Add OpenGL ZBC clear support and refactor ZBC management

Moved ZBCManager to `video_core/zbc_manager.cpp/h` for modularity. Added
`gl_zbc_clear.cpp/h` for efficient OpenGL color, depth, and stencil clears.
Updated `RasterizerOpenGL::Clear` to use ZBC with fallback. Added stencil
type validation. Fixed color mask and logging. Updated `CMakeLists.txt`.
Enhances performance and code organization.

Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
Zephyron
2025-09-14 20:28:39 +10:00
parent 12c3e4b92c
commit 94119302ec
11 changed files with 769 additions and 54 deletions

View File

@@ -10,59 +10,21 @@
#include "core/hle/service/nvdrv/devices/ioctl_serialization.h" #include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
#include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h" #include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
#include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvdrv/nvdrv.h"
#include "video_core/zbc_manager.h"
namespace Service::Nvidia::Devices { namespace Service::Nvidia::Devices {
// ZBC helper functions for GPU clearing operations // ZBC helper functions for GPU clearing operations
namespace ZBC { namespace ZBC {
std::optional<std::array<u32, 4>> GetColor(u32 format, u32 type) { std::optional<std::array<u32, 4>> GetColor(u32 format, u32 type) {
return ZBCManager::Instance().GetZBCColor(format, type); return VideoCore::ZBCManager::Instance().GetZBCColor(format, type);
} }
std::optional<u32> GetDepth(u32 format, u32 type) { std::optional<u32> GetDepth(u32 format, u32 type) {
return ZBCManager::Instance().GetZBCDepth(format, type); return VideoCore::ZBCManager::Instance().GetZBCDepth(format, type);
} }
} }
// ZBCManager implementation
std::optional<std::array<u32, 4>> 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<u32> 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<u32, 4>& color_ds,
const std::array<u32, 4>& 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_) nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system_, EventInterface& events_interface_)
: nvdevice{system_}, events_interface{events_interface_} { : nvdevice{system_}, events_interface{events_interface_} {
@@ -143,11 +105,11 @@ void nvhost_ctrl_gpu::OnClose(DeviceFD fd) {}
// ZBC table management methods // ZBC table management methods
std::optional<std::array<u32, 4>> nvhost_ctrl_gpu::GetZBCColor(u32 format, u32 type) const { std::optional<std::array<u32, 4>> nvhost_ctrl_gpu::GetZBCColor(u32 format, u32 type) const {
return ZBCManager::Instance().GetZBCColor(format, type); return VideoCore::ZBCManager::Instance().GetZBCColor(format, type);
} }
std::optional<u32> nvhost_ctrl_gpu::GetZBCDepth(u32 format, u32 type) const { std::optional<u32> 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) { void nvhost_ctrl_gpu::StoreZBCEntry(const IoctlZbcSetTable& params) {
@@ -166,7 +128,7 @@ void nvhost_ctrl_gpu::StoreZBCEntry(const IoctlZbcSetTable& params) {
zbc_table[key] = entry; zbc_table[key] = entry;
// Also store in global ZBCManager for GPU access // 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}", LOG_DEBUG(Service_NVDRV, "Stored ZBC entry: format=0x{:X}, type=0x{:X}, depth=0x{:X}",
params.format, params.type, params.depth); params.format, params.type, params.depth);
@@ -331,8 +293,8 @@ NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) {
return NvResult::BadParameter; return NvResult::BadParameter;
} }
// Validate the type parameter (typically 0 for color, 1 for depth) // Validate the type parameter (0=color, 1=depth, 2=stencil)
if (params.type > 1) { if (params.type > 2) {
LOG_WARNING(Service_NVDRV, "Invalid ZBC type: 0x{:X}", params.type); LOG_WARNING(Service_NVDRV, "Invalid ZBC type: 0x{:X}", params.type);
return NvResult::BadParameter; 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}]", 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]); 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}]", 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; return NvResult::Success;
} }

View File

@@ -33,6 +33,8 @@ add_library(video_core STATIC
delayed_destruction_ring.h delayed_destruction_ring.h
dirty_flags.cpp dirty_flags.cpp
dirty_flags.h dirty_flags.h
zbc_manager.cpp
zbc_manager.h
dma_pusher.cpp dma_pusher.cpp
dma_pusher.h dma_pusher.h
engines/sw_blitter/blitter.cpp engines/sw_blitter/blitter.cpp
@@ -152,6 +154,8 @@ add_library(video_core STATIC
renderer_opengl/gl_graphics_pipeline.h renderer_opengl/gl_graphics_pipeline.h
renderer_opengl/gl_rasterizer.cpp renderer_opengl/gl_rasterizer.cpp
renderer_opengl/gl_rasterizer.h 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.cpp
renderer_opengl/gl_resource_manager.h renderer_opengl/gl_resource_manager.h
renderer_opengl/gl_shader_cache.cpp renderer_opengl/gl_shader_cache.cpp
@@ -197,6 +201,8 @@ add_library(video_core STATIC
renderer_vulkan/present/window_adapt_pass.h renderer_vulkan/present/window_adapt_pass.h
renderer_vulkan/blit_image.cpp renderer_vulkan/blit_image.cpp
renderer_vulkan/blit_image.h 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.cpp
renderer_vulkan/fixed_pipeline_state.h renderer_vulkan/fixed_pipeline_state.h
renderer_vulkan/maxwell_to_vk.cpp renderer_vulkan/maxwell_to_vk.cpp

View File

@@ -23,6 +23,7 @@
#include "video_core/renderer_opengl/gl_device.h" #include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_query_cache.h" #include "video_core/renderer_opengl/gl_query_cache.h"
#include "video_core/renderer_opengl/gl_rasterizer.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_shader_cache.h"
#include "video_core/renderer_opengl/gl_staging_buffer_pool.h" #include "video_core/renderer_opengl/gl_staging_buffer_pool.h"
#include "video_core/renderer_opengl/gl_texture_cache.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); 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) { 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<u32>(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) { if (use_depth && use_stencil) {
glClearBufferfi(GL_DEPTH_STENCIL, 0, regs.clear_depth, regs.clear_stencil); const u32 format = static_cast<u32>(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) { } else if (use_depth) {
glClearBufferfv(GL_DEPTH, 0, &regs.clear_depth); const u32 format = static_cast<u32>(regs.zeta.format);
const u32 type = 1; // Depth clear type
if (!OpenGL::ZBCClear::ClearDepth(format, type)) {
glClearBufferfv(GL_DEPTH, 0, &regs.clear_depth);
} else {
zbc_used = true;
}
} else if (use_stencil) { } else if (use_stencil) {
glClearBufferiv(GL_STENCIL, 0, &regs.clear_stencil); // Try ZBC stencil clear first, fall back to regular clear if not available
const u32 format = static_cast<u32>(regs.zeta.format);
const u32 type = 2; // Stencil clear type
if (!OpenGL::ZBCClear::ClearStencil(format, type, regs.clear_stencil)) {
glClearBufferiv(GL_STENCIL, 0, &regs.clear_stencil);
} else {
zbc_used = true;
}
}
if (zbc_used) {
LOG_TRACE(Render_OpenGL, "ZBC: Used ZBC clear for efficient buffer clearing");
} }
++num_queued_commands; ++num_queued_commands;
} }
@@ -1101,7 +1137,7 @@ void RasterizerOpenGL::SyncColorMask() {
flags[Dirty::ColorMask0] = false; flags[Dirty::ColorMask0] = false;
auto& mask = regs.color_mask[0]; 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; return;
} }

View File

@@ -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 <glad/glad.h>
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<GLint>(stencil_value));
return true;
}
std::optional<std::array<f32, 4>> 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<f32> 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<f32, 4> ZBCClear::ConvertColorToOpenGL(const std::array<u32, 4>& color_u32) {
// Convert ZBC color values to OpenGL format
// The ZBC color values are typically in RGBA8888 format
std::array<f32, 4> 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<f32>(r) / 255.0f; // Red
color_f32[1] = static_cast<f32>(g) / 255.0f; // Green
color_f32[2] = static_cast<f32>(b) / 255.0f; // Blue
color_f32[3] = static_cast<f32>(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<const GLint*>(&clear_stencil));
return true;
}
} // namespace OpenGL

View File

@@ -0,0 +1,82 @@
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <optional>
#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<std::array<f32, 4>> 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<f32> 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<f32, 4> ConvertColorToOpenGL(const std::array<u32, 4>& color_u32);
};
} // namespace OpenGL

View File

@@ -4,6 +4,7 @@
#include <algorithm> #include <algorithm>
#include "video_core/renderer_vulkan/vk_texture_cache.h" #include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/renderer_vulkan/vk_zbc_clear.h"
#include "common/settings.h" #include "common/settings.h"
#include "video_core/host_shaders/blit_color_float_frag_spv.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, void BlitImageHelper::ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask,
const std::array<f32, 4>& clear_color, const std::array<f32, 4>& clear_color,
const Region2D& dst_region) { 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{ const BlitImagePipelineKey key{
.renderpass = dst_framebuffer->RenderPass(), .renderpass = dst_framebuffer->RenderPass(),
.operation = Tegra::Engines::Fermi2D::Operation::BlendPremult, .operation = Tegra::Engines::Fermi2D::Operation::BlendPremult,

View File

@@ -35,7 +35,7 @@ VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
switch (framebuffer.pixel_format) { switch (framebuffer.pixel_format) {
case Service::android::PixelFormat::Rgba8888: case Service::android::PixelFormat::Rgba8888:
case Service::android::PixelFormat::Rgbx8888: case Service::android::PixelFormat::Rgbx8888:
return VK_FORMAT_A8B8G8R8_UNORM_PACK32; return VK_FORMAT_R8G8B8A8_UNORM;
case Service::android::PixelFormat::Rgb565: case Service::android::PixelFormat::Rgb565:
return VK_FORMAT_R5G6B5_UNORM_PACK16; return VK_FORMAT_R5G6B5_UNORM_PACK16;
case Service::android::PixelFormat::Bgra8888: case Service::android::PixelFormat::Bgra8888:
@@ -43,7 +43,7 @@ VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
default: default:
UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}", UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}",
static_cast<u32>(framebuffer.pixel_format)); static_cast<u32>(framebuffer.pixel_format));
return VK_FORMAT_A8B8G8R8_UNORM_PACK32; return VK_FORMAT_R8G8B8A8_UNORM;
} }
} }

View File

@@ -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<VkImageSubresourceRange, 1> 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<VkClearColorValue> 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<f32> 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<u32, 4>& 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<f32>(r) / 255.0f; // Red
clear_color.float32[1] = static_cast<f32>(g) / 255.0f; // Green
clear_color.float32[2] = static_cast<f32>(b) / 255.0f; // Blue
clear_color.float32[3] = static_cast<f32>(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

View File

@@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <optional>
#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<VkClearColorValue> 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<f32> 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<u32, 4>& color_u32);
};
} // namespace Vulkan

View File

@@ -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<std::array<u32, 4>> 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<u32> 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<u32, 4>& color_ds,
const std::array<u32, 4>& 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<f32, 4> ZBCManager::ConvertToFloat(const std::array<u32, 4>& color_u32) {
std::array<f32, 4> 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<f32>(r) / 255.0f; // Red
color_f32[1] = static_cast<f32>(g) / 255.0f; // Green
color_f32[2] = static_cast<f32>(b) / 255.0f; // Blue
color_f32[3] = static_cast<f32>(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<f32>(first_color) / static_cast<f32>(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<f32>(depth_u32) / static_cast<f32>(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

View File

@@ -0,0 +1,111 @@
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <optional>
#include <mutex>
#include <map>
#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<u32, 4> color_ds; // Direct Surface color values
std::array<u32, 4> 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<std::array<u32, 4>> 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<u32> 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<u32, 4>& color_ds,
const std::array<u32, 4>& 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<f32, 4> ConvertToFloat(const std::array<u32, 4>& 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<std::pair<u32, u32>, ZBCEntry> zbc_table; // Key: (format, type)
};
} // namespace VideoCore