Files
wiicompiled/aurora-main/lib/gfx/tex_copy_conv.cpp
T
patchzyy 6f14bde26a Kartpad upstream fixes (#244)
* Preserve interrupted registers and unwind alarm guards before rescheduling

Adapt the RFL interrupt-context and alarm reschedule fixes from KartPad ed8e4ca and 0c9bff0. Keep caller registers private and release the recursion guard before a woken fiber can pump callbacks.

* Keep local Wii identity services available when networking is disabled

Adapt KartPad a0f3fb5. Only IP and SSL devices require network access; KD request/time and NCD management remain available for offline save and license initialization.

* Share repeated LR continuation dispatch in translated functions

Adapt KartPad be91d8f/a3f90eb without its floating-point ABI changes. Preserve upstream continuation discovery and all resume labels. Validation: 640 translator tests passed.

* Reject inconsistent GPU cache sizes before allocation or copying

Adapt KartPad runtime 70951022. Validate raw lengths, compression tags and Zstd frame lengths on the size probe as well as the fetch. Tested against malformed SQLite rows and valid raw/compressed round trips.

* Wake compiler workers when pipeline work becomes runnable

Adapt KartPad runtime 956d811e. Wake all consumers of the shared condition variable after queue insertion or promotion; retain upstream desktop prewarm policy. A blocked-compiler probe verified progress by an idle worker.

* Reuse and release one Metal view per SDL window

Adapt KartPad 3606741. Surface recreation reuses the existing view and window property cleanup owns its lifetime. Reviewed against SDL3 cleanup semantics; Apple hardware validation remains outstanding.

* Avoid overreading packed three-byte vertex attributes

Adapt KartPad 0f6b274. Do not read a second storage word when all three requested bytes fit in the first. Preserve upstream depth and fog corrections.

* Keep interpolation history within each split-screen viewport

Adapt KartPad d6299b5. Scope exact, material and sibling-palette matching to the logical viewport so identical meshes from different cameras cannot share transforms.

* Report graphics startup failures and safely clean up partial ImGui initialization

Adapt KartPad runtime 70dc9380 and c4566e50 using the existing WiiCompiled exception/reporting path. A dummy-video-driver probe verified error return and repeated partial shutdown without aborting.

* Preserve GX draw boundaries and GPU staging and readback state

Adapt the validated renderer fixes from KartPad runtime 31add0c3, 7393dafe, b7f515de, cf46a9c7, fad42a7b, 7cd09b69, 9feea6b2 and Android 2505ae22 to current upstream. Preserve complete primitives and fresh vertex layouts, split staging batches before overflow, retain offscreen state, scope asynchronous callbacks and frame state, and complete texture-copy sources.

Add unit regressions and an optional ROM-free GPU pixel test. Validation: 250 GX tests and actual D3D12 pixel/readback, capacity, interpolation and frame-worker checks passed with Dawn validation enabled.
2026-09-23 19:27:51 +02:00

617 lines
21 KiB
C++

#include "tex_copy_conv.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>
#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;
// 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 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}) {
g_blitPipelines[format] = create_pipeline(
{GX_TF_RGBA8, FragPassthrough, format, "TexCopyConv Blit"}, ShaderPreamble, 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_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(),
};
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) {
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) {
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