mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 13:00:26 +02:00
- Introduced a new configuration option for immersive window mode in runtime_config.h. - Updated the parsing and setting functions to handle the immersive window state. - Modified the OpenXR backend to support rendering with the immersive window, blending the race view with the surrounding environment. - Enhanced the settings overlay to allow users to select between immersive, immersive window, and flat screen race views. - Implemented GPU rendering logic for the immersive window mask, ensuring correct visual output in various rendering paths. - Added tests to validate the immersive window functionality and its interaction with existing race view settings.
209 lines
9.0 KiB
C++
209 lines
9.0 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
// The immersive window (AuroraStereoFrame::window): after an eye's last draw,
|
|
// one full-screen triangle keeps what the eye sees through the 2D layer's
|
|
// screen and makes the rest transparent black, so the compositor shows its own
|
|
// background (the room, with passthrough) around the race. See OPENXR.md, "The
|
|
// immersive window".
|
|
#pragma once
|
|
|
|
#include "common.hpp"
|
|
#include "stereo_replay.hpp"
|
|
#include "../webgpu/gpu.hpp"
|
|
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <utility>
|
|
|
|
namespace aurora::gfx::window_mask {
|
|
|
|
struct Vertex {
|
|
float position[2];
|
|
// stereo_replay::WindowMask's (u, v, w) at this corner.
|
|
float window[3];
|
|
};
|
|
|
|
struct PipelineKey {
|
|
wgpu::TextureFormat format = wgpu::TextureFormat::Undefined;
|
|
// Undefined: a pass with no depth attachment.
|
|
wgpu::TextureFormat depthFormat = wgpu::TextureFormat::Undefined;
|
|
uint32_t samples = 0;
|
|
|
|
bool operator==(const PipelineKey&) const = default;
|
|
};
|
|
|
|
// The eye passes (with depth) use one attachment layout and the mono fallback (without) another;
|
|
// each keeps its own pipeline, rebuilt when its format or sample count changes.
|
|
inline std::array<std::pair<PipelineKey, wgpu::RenderPipeline>, 2> pipelines;
|
|
inline std::array<wgpu::Buffer, AURORA_STEREO_EYE_COUNT> vertexBuffers;
|
|
|
|
inline void shutdown() {
|
|
pipelines = {};
|
|
vertexBuffers = {};
|
|
}
|
|
|
|
inline const wgpu::RenderPipeline& pipeline(const PipelineKey& key) {
|
|
for (const auto& [cachedKey, cached] : pipelines) {
|
|
if (cached && cachedKey == key) {
|
|
return cached;
|
|
}
|
|
}
|
|
wgpu::ShaderSourceWGSL source{};
|
|
source.code = R"(
|
|
struct Out { @builtin(position) position: vec4f, @location(0) window: vec3f };
|
|
@vertex fn vs(@location(0) position: vec2f, @location(1) window: vec3f) -> Out {
|
|
var o: Out;
|
|
o.position = vec4f(position, 0.5, 1.0);
|
|
o.window = window;
|
|
return o;
|
|
}
|
|
@fragment fn fs(i: Out) -> @location(0) vec4f {
|
|
// Where this pixel's ray meets the screen's plane, in its half extents.
|
|
let p = i.window.xy / i.window.z;
|
|
let edge = 1.0 - abs(p);
|
|
// One pixel of coverage ramp at the edge, from the screen-space rate of change.
|
|
let ramp = clamp(edge / max(fwidth(edge), vec2f(1e-6)) + 0.5, vec2f(0.0), vec2f(1.0));
|
|
// Behind the eye (w <= 0), or far off the screen where the plane nears the horizon.
|
|
let seen = i.window.z > 0.0 && all(abs(p) < vec2f(2.0));
|
|
return vec4f(0.0, 0.0, 0.0, select(0.0, ramp.x * ramp.y, seen));
|
|
}
|
|
)";
|
|
wgpu::ShaderModuleDescriptor moduleDescriptor{};
|
|
moduleDescriptor.nextInChain = &source;
|
|
moduleDescriptor.label = "VR immersive window mask";
|
|
const auto shader = webgpu::g_device.CreateShaderModule(&moduleDescriptor);
|
|
const wgpu::VertexAttribute attributes[] = {
|
|
{.format = wgpu::VertexFormat::Float32x2, .offset = 0, .shaderLocation = 0},
|
|
{.format = wgpu::VertexFormat::Float32x3, .offset = offsetof(Vertex, window), .shaderLocation = 1},
|
|
};
|
|
const wgpu::VertexBufferLayout layout{.arrayStride = sizeof(Vertex), .attributeCount = 2, .attributes = attributes};
|
|
// Premultiplied alpha: the colour is scaled by the coverage the fragment returns as alpha, and the
|
|
// alpha becomes that coverage, whatever the game left there.
|
|
const wgpu::BlendState blend{
|
|
.color = {.operation = wgpu::BlendOperation::Add,
|
|
.srcFactor = wgpu::BlendFactor::Zero,
|
|
.dstFactor = wgpu::BlendFactor::SrcAlpha},
|
|
.alpha = {.operation = wgpu::BlendOperation::Add,
|
|
.srcFactor = wgpu::BlendFactor::One,
|
|
.dstFactor = wgpu::BlendFactor::Zero},
|
|
};
|
|
const wgpu::ColorTargetState color{.format = key.format, .blend = &blend};
|
|
const wgpu::FragmentState fragment{.module = shader, .entryPoint = "fs", .targetCount = 1, .targets = &color};
|
|
const wgpu::DepthStencilState depth{
|
|
.format = key.depthFormat,
|
|
.depthWriteEnabled = false,
|
|
.depthCompare = wgpu::CompareFunction::Always,
|
|
.stencilReadMask = 0,
|
|
.stencilWriteMask = 0,
|
|
};
|
|
wgpu::RenderPipelineDescriptor descriptor{};
|
|
descriptor.label = "VR immersive window mask";
|
|
descriptor.vertex = {.module = shader, .entryPoint = "vs", .bufferCount = 1, .buffers = &layout};
|
|
descriptor.fragment = &fragment;
|
|
descriptor.depthStencil = key.depthFormat == wgpu::TextureFormat::Undefined ? nullptr : &depth;
|
|
descriptor.multisample.count = key.samples;
|
|
descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList;
|
|
auto& slot = key.depthFormat == wgpu::TextureFormat::Undefined ? pipelines[1] : pipelines[0];
|
|
slot = {key, webgpu::g_device.CreateRenderPipeline(&descriptor)};
|
|
return slot.second;
|
|
}
|
|
|
|
// Writes the eye's triangle: NDC corners (-1, -1), (3, -1) and (-1, 3) cover the whole image, with
|
|
// the mask's rows evaluated at each so they interpolate across it exactly.
|
|
inline const wgpu::Buffer& eye_vertices(uint32_t eye, const stereo_replay::WindowMask& mask) {
|
|
static constexpr std::array<std::array<float, 2>, 3> kCorners{{{-1.0f, -1.0f}, {3.0f, -1.0f}, {-1.0f, 3.0f}}};
|
|
std::array<Vertex, 3> vertices{};
|
|
for (size_t i = 0; i < vertices.size(); ++i) {
|
|
const auto h = mask.at(kCorners[i][0], kCorners[i][1]);
|
|
vertices[i] = {{kCorners[i][0], kCorners[i][1]}, {h.x, h.y, h.z}};
|
|
}
|
|
auto& buffer = vertexBuffers[eye];
|
|
if (!buffer) {
|
|
const wgpu::BufferDescriptor descriptor{
|
|
.label = "VR immersive window mask vertices",
|
|
.usage = wgpu::BufferUsage::Vertex | wgpu::BufferUsage::CopyDst,
|
|
.size = sizeof(vertices),
|
|
};
|
|
buffer = webgpu::g_device.CreateBuffer(&descriptor);
|
|
}
|
|
webgpu::g_queue.WriteBuffer(buffer, 0, vertices.data(), sizeof(vertices));
|
|
return buffer;
|
|
}
|
|
|
|
// Masks one eye inside a render pass already open on its attachments, after everything else it draws.
|
|
inline void draw(const wgpu::RenderPassEncoder& pass, const StereoReplayFrame& frame, uint32_t eye,
|
|
const stereo_replay::HudScreen& screen) {
|
|
const auto& view = frame.eyes[eye];
|
|
const auto& target = view.target;
|
|
const PipelineKey key{
|
|
.format = webgpu::g_graphicsConfig.surfaceConfiguration.format,
|
|
.depthFormat = target.depthFormat,
|
|
.samples = target.msaaSamples,
|
|
};
|
|
const auto& buffer = eye_vertices(eye, stereo_replay::window_mask(view.projection, view.viewFromCenter, screen));
|
|
pass.SetViewport(0.0f, 0.0f, static_cast<float>(target.size.width), static_cast<float>(target.size.height), 0.0f,
|
|
1.0f);
|
|
pass.SetScissorRect(0, 0, target.size.width, target.size.height);
|
|
pass.SetPipeline(pipeline(key));
|
|
pass.SetVertexBuffer(0, buffer);
|
|
pass.Draw(3);
|
|
}
|
|
|
|
// The same in a render pass of its own, over the finished eye's attachments.
|
|
inline void render(wgpu::CommandEncoder& cmd, const StereoReplayFrame& frame, uint32_t eye,
|
|
const stereo_replay::HudScreen& screen) {
|
|
const auto& target = frame.eyes[eye].target;
|
|
const bool stencil = target.depthFormat == wgpu::TextureFormat::Depth24PlusStencil8;
|
|
const wgpu::RenderPassColorAttachment color{
|
|
.view = target.colorView,
|
|
.resolveTarget = target.resolveView,
|
|
.loadOp = wgpu::LoadOp::Load,
|
|
.storeOp = wgpu::StoreOp::Store,
|
|
};
|
|
const wgpu::RenderPassDepthStencilAttachment depth{
|
|
.view = target.depthView,
|
|
.depthLoadOp = wgpu::LoadOp::Load,
|
|
.depthStoreOp = wgpu::StoreOp::Store,
|
|
.stencilLoadOp = stencil ? wgpu::LoadOp::Load : wgpu::LoadOp::Undefined,
|
|
.stencilStoreOp = stencil ? wgpu::StoreOp::Store : wgpu::StoreOp::Undefined,
|
|
};
|
|
const wgpu::RenderPassDescriptor descriptor{
|
|
.label = "VR immersive window mask",
|
|
.colorAttachmentCount = 1,
|
|
.colorAttachments = &color,
|
|
.depthStencilAttachment = &depth,
|
|
};
|
|
const auto pass = cmd.BeginRenderPass(&descriptor);
|
|
draw(pass, frame, eye, screen);
|
|
pass.End();
|
|
}
|
|
|
|
// Masks an eye image that holds the duplicated mono picture (a frame whose stereo replay could not
|
|
// be prepared): only the resolved output exists there, with no depth.
|
|
inline void render_output(wgpu::CommandEncoder& cmd, const StereoReplayFrame& frame, uint32_t eye,
|
|
const wgpu::TextureView& output, wgpu::Extent3D size,
|
|
const stereo_replay::HudScreen& screen) {
|
|
const auto& view = frame.eyes[eye];
|
|
const PipelineKey key{.format = webgpu::g_graphicsConfig.surfaceConfiguration.format, .samples = 1};
|
|
const auto& buffer = eye_vertices(eye, stereo_replay::window_mask(view.projection, view.viewFromCenter, screen));
|
|
const wgpu::RenderPassColorAttachment color{
|
|
.view = output,
|
|
.loadOp = wgpu::LoadOp::Load,
|
|
.storeOp = wgpu::StoreOp::Store,
|
|
};
|
|
const wgpu::RenderPassDescriptor descriptor{
|
|
.label = "VR immersive window mask",
|
|
.colorAttachmentCount = 1,
|
|
.colorAttachments = &color,
|
|
};
|
|
const auto pass = cmd.BeginRenderPass(&descriptor);
|
|
pass.SetViewport(0.0f, 0.0f, static_cast<float>(size.width), static_cast<float>(size.height), 0.0f, 1.0f);
|
|
pass.SetScissorRect(0, 0, size.width, size.height);
|
|
pass.SetPipeline(pipeline(key));
|
|
pass.SetVertexBuffer(0, buffer);
|
|
pass.Draw(3);
|
|
pass.End();
|
|
}
|
|
|
|
} // namespace aurora::gfx::window_mask
|