Files
mitch030504--Wiicompiled_VR…/aurora-main/lib/gfx/tex_copy_conv.cpp
T
Claude a23fd087ee Fix native EFB readback blits on BGRA surfaces and their Y clamp
The shared blit shader clamps the sampled Y to flags.z..w, but the
native readback uniform set flags.w to 0, so every row sampled row 0.
The blit pipeline was also built only for the surface format, while the
readback draws into an RGBA8 texture; on a BGRA surface (the usual
Linux/Vulkan choice) the pipeline and attachment formats disagree, which
release builds no longer catch because Dawn validation is skipped.

Build blit pipelines for RGBA8, BGRA8 and the surface format, and pick
one by destination format. The Quest 1 simple blit path is unchanged.

From upstream patchzyy/Wiicompiled 6f14bde (#244, KartPad batch).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wg7mB8ogCWmp9GH19Uc82B
2026-10-05 13:31:08 +00:00

699 lines
24 KiB
C++

#include "tex_copy_conv.hpp"
#include "common.hpp"
#include "tex_copy_format_contract.hpp"
#include "../internal.hpp"
#include "../gx/gx.hpp"
#include "../webgpu/gpu.hpp"
#include "texture.hpp"
#include "../gx/gx_fmt.hpp"
#include <absl/container/flat_hash_map.h>
#if defined(__ANDROID__)
#include <sys/system_properties.h>
#endif
#include <cstring>
#include "texture_convert.hpp"
using namespace std::string_literals;
namespace aurora::gfx::tex_copy_conv {
static Module Log("aurora::gfx::tex_copy_conv");
using webgpu::g_device;
static constexpr std::string_view ShaderPreamble = R"(
@group(0) @binding(0) var src_samp: sampler;
@group(0) @binding(1) var src: texture_2d<f32>;
struct UVTransform {
offset: vec2f,
scale: vec2f,
copy_filter: vec4f,
flags: vec4f,
};
@group(0) @binding(2) var<uniform> uv_xf: UVTransform;
struct VertexOutput {
@builtin(position) pos: vec4f,
@location(0) uv: vec2f,
};
var<private> positions: array<vec2f, 3> = array(
vec2f(-1.0, 1.0),
vec2f(-1.0, -3.0),
vec2f(3.0, 1.0),
);
var<private> uvs: array<vec2f, 3> = array(
vec2f(0.0, 0.0),
vec2f(0.0, 2.0),
vec2f(2.0, 0.0),
);
@vertex fn vs_main(@builtin(vertex_index) vi: u32) -> VertexOutput {
var out: VertexOutput;
out.pos = vec4f(positions[vi], 0.0, 1.0);
out.uv = uvs[vi] * uv_xf.scale + uv_xf.offset;
return out;
}
fn intensity(rgb: vec3f) -> f32 {
// ITU-R BT.601 luma coefficients
return dot(rgb, vec3f(0.257, 0.504, 0.098)) + 16.0 / 255.0;
}
fn quantize4(v: f32) -> f32 {
return floor(v * 16.0) / 15.0;
}
fn apply_opaque_alpha(c: vec4f) -> vec4f {
if (uv_xf.flags.x != 0.0) {
return vec4f(c.rgb, 1.0);
}
return c;
}
fn clamp_copy_uv(uv: vec2f) -> vec2f {
return vec2f(uv.x, clamp(uv.y, uv_xf.flags.z, uv_xf.flags.w));
}
fn sample_copy(uv: vec2f) -> vec4f {
let current = textureSample(src, src_samp, clamp_copy_uv(uv));
if (uv_xf.copy_filter.w == 0.0) {
return apply_opaque_alpha(current);
}
let tex_size = vec2f(textureDimensions(src));
let pixel_size = vec2f(1.0, 1.0) / tex_size;
let row_stride = max(uv_xf.flags.y, 1.0);
let prev = textureSample(src, src_samp,
clamp_copy_uv(uv - vec2f(0.0, pixel_size.y * row_stride)));
let next = textureSample(src, src_samp,
clamp_copy_uv(uv + vec2f(0.0, pixel_size.y * row_stride)));
let prev_rgb = floor(prev.rgb * 255.0 + vec3f(0.5));
let current_rgb = floor(current.rgb * 255.0 + vec3f(0.5));
let next_rgb = floor(next.rgb * 255.0 + vec3f(0.5));
let filtered_rgb = min(
floor((prev_rgb * uv_xf.copy_filter.x +
current_rgb * uv_xf.copy_filter.y +
next_rgb * uv_xf.copy_filter.z) / 64.0),
vec3f(255.0));
let filtered = vec4f(filtered_rgb / 255.0, current.a);
return apply_opaque_alpha(filtered);
}
)"sv;
static const std::string DepthShaderPreamble = R"(
@group(0) @binding(0) var src: texture_depth_2d;
struct UVTransform {
offset: vec2f,
scale: vec2f,
copy_filter: vec4f,
flags: vec4f,
};
@group(0) @binding(1) var<uniform> uv_xf: UVTransform;
struct VertexOutput {
@builtin(position) pos: vec4f,
@location(0) uv: vec2f,
};
var<private> positions: array<vec2f, 3> = array(
vec2f(-1.0, 1.0),
vec2f(-1.0, -3.0),
vec2f(3.0, 1.0),
);
var<private> uvs: array<vec2f, 3> = array(
vec2f(0.0, 0.0),
vec2f(0.0, 2.0),
vec2f(2.0, 0.0),
);
@vertex fn vs_main(@builtin(vertex_index) vi: u32) -> VertexOutput {
var out: VertexOutput;
out.pos = vec4f(positions[vi], 0.0, 1.0);
out.uv = uvs[vi] * uv_xf.scale + uv_xf.offset;
return out;
}
)"s + (gx::UseReversedZ ? R"(
fn gx_z24_at_coord(unclamped_coord: vec2i) -> u32 {
let tex_size = vec2i(textureDimensions(src));
let coord = clamp(unclamped_coord, vec2i(0), tex_size - vec2i(1));
let depth = textureLoad(src, coord, 0);
return min(u32(clamp(1.0 - depth, 0.0, 1.0) * 16777215.0 + 0.5), 0x00ffffffu);
}
)"s
: R"(
fn gx_z24_at_coord(unclamped_coord: vec2i) -> u32 {
let tex_size = vec2i(textureDimensions(src));
let coord = clamp(unclamped_coord, vec2i(0), tex_size - vec2i(1));
let depth = textureLoad(src, coord, 0);
return min(u32(clamp(depth, 0.0, 1.0) * 16777215.0 + 0.5), 0x00ffffffu);
}
)"s) + R"(
fn gx_depth_bytes(z24: u32) -> vec3u {
return vec3u((z24 >> 16u) & 0xffu, (z24 >> 8u) & 0xffu, z24 & 0xffu);
}
fn clamp_copy_coord(coord: vec2i, tex_size: vec2i) -> vec2i {
let top = i32(floor(uv_xf.flags.z * f32(tex_size.y)));
let bottom = i32(floor(uv_xf.flags.w * f32(tex_size.y)));
return vec2i(coord.x, clamp(coord.y, top, bottom));
}
fn sample_depth_copy(uv: vec2f) -> vec4u {
let tex_size = vec2i(textureDimensions(src));
let current_coord = clamp_copy_coord(vec2i(floor(uv * vec2f(tex_size))), tex_size);
let current = gx_depth_bytes(gx_z24_at_coord(current_coord));
if (uv_xf.copy_filter.w == 0.0) {
return vec4u(current, 255u);
}
// GX applies its vertical copy filter to depth copies too, filtering the high/middle/low Z bytes
// independently before the destination format picks bytes. Alpha is unfiltered and 255.
let row_stride = max(i32(round(uv_xf.flags.y)), 1);
let prev = gx_depth_bytes(gx_z24_at_coord(
clamp_copy_coord(current_coord - vec2i(0, row_stride), tex_size)));
let next = gx_depth_bytes(gx_z24_at_coord(
clamp_copy_coord(current_coord + vec2i(0, row_stride), tex_size)));
let coefficients = vec3u(uv_xf.copy_filter.xyz);
let combined = prev * coefficients.x + current * coefficients.y + next * coefficients.z;
var filtered = combined >> vec3u(6u);
// The copy-filter accumulator wraps to nine bits when coefficients can
// produce values at or above 512, before saturating to an eight-bit byte.
if (coefficients.x + coefficients.y + coefficients.z >= 128u) {
filtered = filtered & vec3u(0x1ffu);
}
filtered = min(filtered, vec3u(255u));
return vec4u(filtered, 255u);
}
)"s;
// Passthrough blit (for scaling)
static constexpr std::string_view FragPassthrough = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
return sample_copy(in.uv);
}
)"sv;
// The Quest 1's Adreno compiler misrenders the general copy shader, whose
// sample helper contains texture-dimension math and a dynamic filter branch,
// even for passthrough RGBA copies. This equivalent shader deliberately keeps
// the same bind-group contract while omitting that unused filter machinery.
static constexpr std::string_view SimpleBlitShader = R"(
@group(0) @binding(0) var src_samp: sampler;
@group(0) @binding(1) var src: texture_2d<f32>;
struct UVTransform {
offset: vec2f,
scale: vec2f,
copy_filter: vec4f,
flags: vec4f,
};
@group(0) @binding(2) var<uniform> uv_xf: UVTransform;
struct VertexOutput {
@builtin(position) pos: vec4f,
@location(0) uv: vec2f,
};
var<private> positions: array<vec2f, 3> = array(
vec2f(-1.0, 1.0), vec2f(-1.0, -3.0), vec2f(3.0, 1.0));
var<private> uvs: array<vec2f, 3> = array(
vec2f(0.0, 0.0), vec2f(0.0, 2.0), vec2f(2.0, 0.0));
@vertex fn vs_main(@builtin(vertex_index) vi: u32) -> VertexOutput {
var out: VertexOutput;
out.pos = vec4f(positions[vi], 0.0, 1.0);
out.uv = uvs[vi] * uv_xf.scale + uv_xf.offset;
return out;
}
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let color = textureSample(src, src_samp, in.uv);
if (uv_xf.flags.x != 0.0) {
return vec4f(color.rgb, 1.0);
}
return color;
}
)"sv;
// GX_TF_I4: 4-bit intensity -> R8Unorm (quantized)
static constexpr std::string_view FragI4 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let rgb = sample_copy(in.uv).rgb;
let i = quantize4(intensity(rgb));
return vec4f(i, i, i, i);
}
)"sv;
// GX_TF_I8: 8-bit intensity -> R8Unorm
static constexpr std::string_view FragI8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let rgb = sample_copy(in.uv).rgb;
let i = intensity(rgb);
return vec4f(i, i, i, i);
}
)"sv;
// GX_TF_IA4: 4-bit intensity + 4-bit alpha -> RG8Unorm
static constexpr std::string_view FragIA4 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let c = sample_copy(in.uv);
let i = quantize4(intensity(c.rgb));
let a = quantize4(c.a);
return vec4f(i, i, i, a);
}
)"sv;
// GX_TF_IA8: 8-bit intensity + 8-bit alpha -> RG8Unorm
static constexpr std::string_view FragIA8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let c = sample_copy(in.uv);
let i = intensity(c.rgb);
return vec4f(i, i, i, c.a);
}
)"sv;
// GX_TF_RGB565: Blit alpha to 1.0
static constexpr std::string_view FragRGB565 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let c = sample_copy(in.uv);
return vec4f(c.rgb, 1.0);
}
)"sv;
// GX_CTF_R4: 4-bit red -> R8Unorm
static constexpr std::string_view FragR4 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let r = quantize4(sample_copy(in.uv).r);
return vec4f(r, r, r, r);
}
)"sv;
// GX_CTF_RA4: 4-bit red + 4-bit alpha -> RG8Unorm
static constexpr std::string_view FragRA4 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let c = sample_copy(in.uv);
let r = quantize4(c.r);
return vec4f(r, r, r, quantize4(c.a));
}
)"sv;
// GX_CTF_RA8: 8-bit red + 8-bit alpha -> RG8Unorm
static constexpr std::string_view FragRA8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let c = sample_copy(in.uv);
return vec4f(c.r, c.r, c.r, c.a);
}
)"sv;
// GX_CTF_A8: 8-bit alpha -> R8Unorm
static constexpr std::string_view FragA8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let a = sample_copy(in.uv).a;
return vec4f(a, a, a, a);
}
)"sv;
// GX_CTF_R8: 8-bit red -> R8Unorm
static constexpr std::string_view FragR8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let r = sample_copy(in.uv).r;
return vec4f(r, r, r, r);
}
)"sv;
// GX_CTF_G8: 8-bit green -> R8Unorm
static constexpr std::string_view FragG8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let g = sample_copy(in.uv).g;
return vec4f(g, g, g, g);
}
)"sv;
// GX_CTF_B8: 8-bit blue -> R8Unorm
static constexpr std::string_view FragB8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let b = sample_copy(in.uv).b;
return vec4f(b, b, b, b);
}
)"sv;
// GX_CTF_RG8: 8-bit red + 8-bit green -> RG8Unorm
static constexpr std::string_view FragRG8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let c = sample_copy(in.uv);
return vec4f(c.r, c.r, c.r, c.g);
}
)"sv;
// GX_CTF_GB8: 8-bit green + 8-bit blue -> RG8Unorm
static constexpr std::string_view FragGB8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let c = sample_copy(in.uv);
return vec4f(c.g, c.g, c.g, c.b);
}
)"sv;
// GX_TF_Z8 stores the high-Z byte in I8 storage, which samples as intensity
// replicated across all four channels.
static constexpr std::string_view FragZ8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let depth_bytes = sample_depth_copy(in.uv);
let z8 = f32(depth_bytes.r) / 255.0;
return vec4f(z8, z8, z8, z8);
}
)"sv;
// GX_TF_Z16 is the depth RA8 copy path: depth samples have opaque alpha and their high-Z byte in
// red, so the encoded [A, R] pair samples through IA8 as high-Z intensity with opaque alpha.
static constexpr std::string_view FragZ16 = detail::Z16FragmentShader;
// GX_TF_Z24X8 uses the RGBA8 storage shape for copies, with the 24-bit depth
// value available to z-texture sampling through RGB.
static constexpr std::string_view FragZ24X8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let depth_bytes = sample_depth_copy(in.uv);
let r = f32(depth_bytes.r) / 255.0;
let g = f32(depth_bytes.g) / 255.0;
let b = f32(depth_bytes.b) / 255.0;
return vec4f(r, g, b, 1.0);
}
)"sv;
struct ConvPipeline {
GXTexFmt fmt;
std::string_view fragShader;
wgpu::TextureFormat outputFormat;
const char* label;
};
static constexpr std::array ConvPipelines{
ConvPipeline{GX_TF_I4, FragI4, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv I4"},
ConvPipeline{GX_TF_I8, FragI8, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv I8"},
ConvPipeline{GX_TF_IA4, FragIA4, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv IA4"},
ConvPipeline{GX_TF_IA8, FragIA8, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv IA8"},
ConvPipeline{GX_TF_RGB565, FragRGB565, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv RGB565"},
ConvPipeline{GX_CTF_R4, FragR4, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv R4"},
ConvPipeline{GX_CTF_RA4, FragRA4, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv RA4"},
ConvPipeline{GX_CTF_RA8, FragRA8, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv RA8"},
ConvPipeline{GX_CTF_A8, FragA8, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv A8"},
ConvPipeline{GX_CTF_R8, FragR8, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv R8"},
ConvPipeline{GX_CTF_G8, FragG8, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv G8"},
ConvPipeline{GX_CTF_B8, FragB8, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv B8"},
ConvPipeline{GX_CTF_RG8, FragRG8, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv RG8"},
ConvPipeline{GX_CTF_GB8, FragGB8, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv GB8"},
};
static constexpr std::array DepthConvPipelines{
ConvPipeline{GX_TF_Z8, FragZ8, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv Z8"},
ConvPipeline{GX_TF_Z16, FragZ16, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv Z16"},
ConvPipeline{GX_TF_Z24X8, FragZ24X8, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv Z24X8"},
};
static wgpu::BindGroupLayout g_bindGroupLayout;
static wgpu::BindGroupLayout g_depthBindGroupLayout;
static wgpu::Sampler g_nearestSampler;
static wgpu::Sampler g_linearSampler;
static absl::flat_hash_map<GXTexFmt, wgpu::RenderPipeline> g_pipelines;
static absl::flat_hash_map<wgpu::TextureFormat, wgpu::RenderPipeline> g_blitPipelines;
static wgpu::RenderPipeline g_quest1BlitPipeline;
static bool quest1_simple_blit() noexcept {
#if defined(__ANDROID__)
static const bool enabled = [] {
char device[PROP_VALUE_MAX]{};
return __system_property_get("ro.product.device", device) > 0 && std::strcmp(device, "monterey") == 0;
}();
return enabled;
#else
return false;
#endif
}
static wgpu::RenderPipeline create_pipeline(const ConvPipeline& conv, const std::string_view shaderPreamble,
const wgpu::BindGroupLayout& bindGroupLayout) {
std::string shaderSource;
shaderSource.reserve(shaderPreamble.size() + conv.fragShader.size());
shaderSource += shaderPreamble;
shaderSource += conv.fragShader;
const wgpu::ShaderSourceWGSL wgslSource{wgpu::ShaderSourceWGSL::Init{
.code = shaderSource.c_str(),
}};
const wgpu::ShaderModuleDescriptor moduleDescriptor{
.nextInChain = &wgslSource,
.label = conv.label,
};
const auto module = g_device.CreateShaderModule(&moduleDescriptor);
const std::array colorTargets{wgpu::ColorTargetState{
.format = conv.outputFormat,
}};
const wgpu::FragmentState fragmentState{
.module = module,
.entryPoint = "fs_main",
.targetCount = colorTargets.size(),
.targets = colorTargets.data(),
};
const wgpu::PipelineLayoutDescriptor layoutDescriptor{
.bindGroupLayoutCount = 1,
.bindGroupLayouts = &bindGroupLayout,
};
const auto pipelineLayout = g_device.CreatePipelineLayout(&layoutDescriptor);
const wgpu::RenderPipelineDescriptor pipelineDescriptor{
.label = conv.label,
.layout = pipelineLayout,
.vertex =
wgpu::VertexState{
.module = module,
.entryPoint = "vs_main",
},
.primitive =
wgpu::PrimitiveState{
.topology = wgpu::PrimitiveTopology::TriangleList,
},
.fragment = &fragmentState,
};
return g_device.CreateRenderPipeline(&pipelineDescriptor);
}
bool needs_conversion(const GXTexFmt fmt) { return g_pipelines.contains(fmt); }
void initialize() {
static constexpr std::array bindGroupLayoutEntries{
wgpu::BindGroupLayoutEntry{
.binding = 0,
.visibility = wgpu::ShaderStage::Fragment,
.sampler =
wgpu::SamplerBindingLayout{
.type = wgpu::SamplerBindingType::Filtering,
},
},
wgpu::BindGroupLayoutEntry{
.binding = 1,
.visibility = wgpu::ShaderStage::Fragment,
.texture =
wgpu::TextureBindingLayout{
.sampleType = wgpu::TextureSampleType::Float,
.viewDimension = wgpu::TextureViewDimension::e2D,
},
},
wgpu::BindGroupLayoutEntry{
.binding = 2,
.visibility = wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment,
.buffer =
wgpu::BufferBindingLayout{
.type = wgpu::BufferBindingType::Uniform,
},
},
};
static constexpr wgpu::BindGroupLayoutDescriptor bindGroupLayoutDescriptor{
.label = "TexCopyConv Bind Group Layout",
.entryCount = bindGroupLayoutEntries.size(),
.entries = bindGroupLayoutEntries.data(),
};
g_bindGroupLayout = g_device.CreateBindGroupLayout(&bindGroupLayoutDescriptor);
static constexpr std::array depthBindGroupLayoutEntries{
wgpu::BindGroupLayoutEntry{
.binding = 0,
.visibility = wgpu::ShaderStage::Fragment,
.texture =
wgpu::TextureBindingLayout{
.sampleType = wgpu::TextureSampleType::Depth,
.viewDimension = wgpu::TextureViewDimension::e2D,
},
},
wgpu::BindGroupLayoutEntry{
.binding = 1,
.visibility = wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment,
.buffer =
wgpu::BufferBindingLayout{
.type = wgpu::BufferBindingType::Uniform,
},
},
};
static constexpr wgpu::BindGroupLayoutDescriptor depthBindGroupLayoutDescriptor{
.label = "TexCopyConv Depth Bind Group Layout",
.entryCount = depthBindGroupLayoutEntries.size(),
.entries = depthBindGroupLayoutEntries.data(),
};
g_depthBindGroupLayout = g_device.CreateBindGroupLayout(&depthBindGroupLayoutDescriptor);
// Native RAM readback uses RGBA even when the EFB/surface uses BGRA.
// Build both variants here; frame workers only read the completed map.
for (const auto format : {wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::BGRA8Unorm,
webgpu::g_graphicsConfig.surfaceConfiguration.format}) {
if (format != wgpu::TextureFormat::Undefined && !g_blitPipelines.contains(format)) {
g_blitPipelines[format] = create_pipeline({GX_TF_RGBA8, FragPassthrough, format, "TexCopyConv Blit"},
ShaderPreamble, g_bindGroupLayout);
}
}
g_quest1BlitPipeline = create_pipeline(
{GX_TF_RGBA8, {}, webgpu::g_graphicsConfig.surfaceConfiguration.format, "Quest 1 Simple TexCopy Blit"},
SimpleBlitShader, g_bindGroupLayout);
for (const auto& conv : ConvPipelines) {
g_pipelines[conv.fmt] = create_pipeline(conv, ShaderPreamble, g_bindGroupLayout);
if (conv.outputFormat != to_wgpu(conv.fmt)) {
Log.fatal("Output format mismatch for {}", conv.fmt);
}
}
for (const auto& conv : DepthConvPipelines) {
g_pipelines[conv.fmt] = create_pipeline(conv, DepthShaderPreamble, g_depthBindGroupLayout);
if (conv.outputFormat != to_wgpu(conv.fmt)) {
Log.fatal("Output format mismatch for {}", conv.fmt);
}
}
static constexpr wgpu::SamplerDescriptor nearestSamplerDescriptor{
.label = "TexCopyConv Nearest Sampler",
.magFilter = wgpu::FilterMode::Nearest,
.minFilter = wgpu::FilterMode::Nearest,
};
g_nearestSampler = g_device.CreateSampler(&nearestSamplerDescriptor);
static constexpr wgpu::SamplerDescriptor linearSamplerDescriptor{
.label = "TexCopyConv Linear Sampler",
.magFilter = wgpu::FilterMode::Linear,
.minFilter = wgpu::FilterMode::Linear,
};
g_linearSampler = g_device.CreateSampler(&linearSamplerDescriptor);
}
void shutdown() {
g_pipelines.clear();
g_blitPipelines.clear();
g_quest1BlitPipeline = {};
g_bindGroupLayout = {};
g_depthBindGroupLayout = {};
g_nearestSampler = {};
g_linearSampler = {};
}
static void execute(const wgpu::CommandEncoder& cmd, const ConvRequest& req, const wgpu::RenderPipeline& pipeline) {
wgpu::BindGroup bindGroup;
if (gx::is_depth_format(req.fmt)) {
const std::array bindGroupEntries{
wgpu::BindGroupEntry{
.binding = 0,
.textureView = req.srcView,
},
wgpu::BindGroupEntry{
.binding = 1,
.buffer = g_uniformBuffer,
.offset = req.uniformRange.offset,
.size = req.uniformRange.size,
},
};
const wgpu::BindGroupDescriptor bindGroupDescriptor{
.layout = g_depthBindGroupLayout,
.entryCount = bindGroupEntries.size(),
.entries = bindGroupEntries.data(),
};
bindGroup = g_device.CreateBindGroup(&bindGroupDescriptor);
} else {
const auto& sampler = req.sampleFilter == SampleFilter::Linear ? g_linearSampler : g_nearestSampler;
const std::array bindGroupEntries{
wgpu::BindGroupEntry{
.binding = 0,
.sampler = sampler,
},
wgpu::BindGroupEntry{
.binding = 1,
.textureView = req.srcView,
},
wgpu::BindGroupEntry{
.binding = 2,
.buffer = g_uniformBuffer,
.offset = req.uniformRange.offset,
.size = req.uniformRange.size,
},
};
const wgpu::BindGroupDescriptor bindGroupDescriptor{
.layout = g_bindGroupLayout,
.entryCount = bindGroupEntries.size(),
.entries = bindGroupEntries.data(),
};
bindGroup = g_device.CreateBindGroup(&bindGroupDescriptor);
}
const std::array colorAttachments{
wgpu::RenderPassColorAttachment{
.view = req.dst->attachmentTextureView,
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store,
.clearValue = {0.0, 0.0, 0.0, 0.0},
},
};
const wgpu::RenderPassDescriptor renderPassDescriptor{
.label = "TexCopyConv Pass",
.colorAttachmentCount = colorAttachments.size(),
.colorAttachments = colorAttachments.data(),
.timestampWrites = gpu_timing_pass(GpuTimingCategory::EfbCopy),
};
const auto pass = cmd.BeginRenderPass(&renderPassDescriptor);
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup);
pass.Draw(3);
pass.End();
}
void run(const wgpu::CommandEncoder& cmd, const ConvRequest& req) {
if (quest1_simple_blit() && req.fmt == GX_TF_RGB5A3) {
// MKW's character/kart menu previews are live RGB5A3 EFB copies. The
// Adreno 540 miscompiles their quantizing conversion shader just like the
// filtered display-copy shader, leaving the preview panels black. Preserve
// the RGBA source directly; only the Wii-era RGB5A3 quantization is lost.
execute(cmd, req, g_quest1BlitPipeline);
return;
}
const auto it = g_pipelines.find(req.fmt);
if (it == g_pipelines.end()) {
Log.fatal("No copy conversion pipeline for format {}", static_cast<int>(req.fmt));
}
execute(cmd, req, it->second);
}
void blit(const wgpu::CommandEncoder& cmd, const ConvRequest& req) {
if (quest1_simple_blit()) {
execute(cmd, req, g_quest1BlitPipeline);
return;
}
const auto it = g_blitPipelines.find(req.dst->format);
if (it == g_blitPipelines.end()) {
Log.fatal("Unsupported blit destination format {}", static_cast<int>(req.dst->format));
}
execute(cmd, req, it->second);
}
} // namespace aurora::gfx::tex_copy_conv