Files
mitch030504--Wiicompiled_VR…/aurora-main/lib/dolphin/gx/GXAurora.cpp
T
iChris4 131da8a7a9 Keep the cockpit wheel with the view and let the native wheel recover
Found on a Quest 3 through Virtual Desktop (VirtualDesktopXR, Vulkan):
the race camera's view matched the scene camera exactly and the kart's
own wheel animated (228 draws a frame took the copy), but during the
race's opening pan no draw took it, the 30-frame fallback latched the VR
wheel for that vehicle, and it stayed until the scene changed.

The fallback now heals: copies keep being published, the VR wheel stands
in only while draws are not taking them, and the vehicle's own wheel
returns as soon as they are. Both switches are logged, a few per race.

With first_person_rotation "yaw_pitch" or "full" the wheel copy and the
hands' wheel geometry now ride in the view's own frame (the kart's
orientation around the seat) instead of the level seat, so the wheel no
longer tilts against the view on slopes.

Aurora logs, after about half a second, five seconds and a minute of
copies, how many draws bound a copied array (inside and outside its
window), how many matched, and the closest position matrix against the
expected one, so a mismatch says whether the array or the matrix differs.
2026-09-22 03:56:38 +02:00

228 lines
8.9 KiB
C++

#include "dolphin/gx/GXAurora.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include "__gx.h"
#include "gx.hpp"
#include "../../window.hpp"
#include "../../gfx/common.hpp"
#include "../../gx/fifo.hpp"
#include "../../gx/native_wheel.hpp"
// GX-thread entry points for the VR native steering wheel (native_wheel.hpp).
// The runtime posts these in order with the frame's draws.
extern "C" void aurora_clear_native_wheel_vertices() {
// Clearing an empty set reports nothing, so a host that clears both before and after a frame's draws keeps
// that frame's count.
if (aurora::gx::nativeWheelArrays.empty()) return;
aurora::gx::fifo::drain();
aurora::gx::nativeWheelLastMatches.store(aurora::gx::nativeWheelMatches);
aurora::gx::nativeWheelMatches = 0;
aurora::gx::nativeWheelPreviousSources.clear();
for (const auto& array : aurora::gx::nativeWheelArrays)
aurora::gx::nativeWheelPreviousSources.push_back(array.source);
aurora::gx::native_wheel_report();
aurora::gx::nativeWheelArrays.clear();
}
extern "C" uint32_t aurora_native_wheel_draw_count() { return aurora::gx::nativeWheelLastMatches.load(); }
extern "C" void aurora_set_native_wheel_vertices(const void* source, const void* replacement, uint32_t size,
const float* modelView) {
if (!source || !replacement || !modelView || !size || size > 65536) return;
aurora::gx::NativeWheelArray array;
array.source = source;
const auto* bytes = static_cast<const uint8_t*>(replacement);
array.bytes.assign(bytes, bytes + size);
std::memcpy(array.modelView.data(), modelView, sizeof(float) * 12);
aurora::gx::nativeWheelArrays.push_back(std::move(array));
}
// Single definition for the `Log` that gx.hpp declares for this directory.
aurora::Module Log("aurora::gx");
namespace aurora::gx {
// Called as a set is cleared: a line at about half a second, five seconds and
// a minute of sets, with the matrices on the first report that saw a draw.
void native_wheel_report() {
auto& diagnostics=nativeWheelDiagnostics;
++diagnostics.sets;
++nativeWheelClears;
if(nativeWheelClears!=30 && nativeWheelClears!=300 && nativeWheelClears!=3600) return;
::Log.info("Native steering wheel: {} sets; draws binding a replaced array {} ({} with a larger range, {} outside a "
"set); matched {}; closest position matrix off by {} ({} matrices)",
diagnostics.sets,diagnostics.boundDraws,diagnostics.oversizeDraws,diagnostics.outsideDraws,
diagnostics.matchedDraws,diagnostics.bestError,diagnostics.bestIndexed?"indexed":"current");
if(diagnostics.boundDraws!=0 && nativeWheelReports++==0) {
const auto& m=diagnostics.bestMatrix;
const auto& e=diagnostics.expected;
::Log.info("Native steering wheel: closest [{} {} {} {} | {} {} {} {} | {} {} {} {}] expected [{} {} {} {} | {} {} "
"{} {} | {} {} {} {}]",
m[0],m[1],m[2],m[3],m[4],m[5],m[6],m[7],m[8],m[9],m[10],m[11],
e[0],e[1],e[2],e[3],e[4],e[5],e[6],e[7],e[8],e[9],e[10],e[11]);
}
diagnostics={};
}
} // namespace aurora::gx
static void GXWriteString(const char* label) {
auto length = strlen(label);
if (length > std::numeric_limits<u16>::max()) {
Log.warn("Debug marker size over u16 max, truncating");
length = std::numeric_limits<u16>::max();
}
GX_WRITE_U16(length);
GX_WRITE_DATA(label, length);
}
void GXPushDebugGroup(const char* label) {
GX_WRITE_AURORA(GX_LOAD_AURORA_DEBUG_GROUP_PUSH);
GXWriteString(label);
}
void GXPopDebugGroup() { GX_WRITE_AURORA(GX_LOAD_AURORA_DEBUG_GROUP_POP); }
void GXInsertDebugMarker(const char* label) {
GX_WRITE_AURORA(GX_LOAD_AURORA_DEBUG_MARKER_INSERT);
GXWriteString(label);
}
void AuroraSetViewportPolicy(AuroraViewportPolicy policy) {
const bool changed = g_gxState.viewportPolicy != policy;
if (changed) {
// Finish commands using the old framebuffer mapping before changing it.
aurora::gx::fifo::drain();
}
g_gxState.viewportPolicy = policy;
aurora::window::set_frame_buffer_aspect_fit(policy == AURORA_VIEWPORT_FIT);
aurora::window::set_present_surface_fill(policy == AURORA_VIEWPORT_STRETCH);
if (changed) {
// Reapply the guest viewport and scissor after a resize.
aurora::gx::set_logical_viewport(g_gxState.logicalViewport);
aurora::gx::set_logical_scissor(g_gxState.logicalScissor);
}
}
void AuroraGetRenderSize(u32* width, u32* height) {
// The guest CPU thread uses this safe render-size value outside render passes.
const auto renderSize = aurora::gfx::get_frame_buffer_size();
if (width != nullptr) {
*width = renderSize.x;
}
if (height != nullptr) {
*height = renderSize.y;
}
}
void AuroraGetSurfaceSize(u32* width, u32* height) {
const auto windowSize = aurora::window::get_window_size();
if (width != nullptr) {
*width = windowSize.native_fb_width;
}
if (height != nullptr) {
*height = windowSize.native_fb_height;
}
}
void GXSetViewportRender(f32 left, f32 top, f32 wd, f32 ht, f32 nearz, f32 farz) {
GX_WRITE_AURORA(GX_LOAD_AURORA_VIEWPORT_RENDER);
GX_WRITE_F32(left);
GX_WRITE_F32(top);
GX_WRITE_F32(wd);
GX_WRITE_F32(ht);
GX_WRITE_F32(nearz);
GX_WRITE_F32(farz);
}
void GXSetScissorRender(u32 left, u32 top, u32 wd, u32 ht) {
GX_WRITE_AURORA(GX_LOAD_AURORA_SCISSOR_RENDER);
GX_WRITE_U32(left);
GX_WRITE_U32(top);
GX_WRITE_U32(wd);
GX_WRITE_U32(ht);
}
namespace {
void WriteMappedRenderState(const aurora::gx::MappedRenderState& mapped) {
GXSetViewportRender(mapped.viewport.left, mapped.viewport.top, mapped.viewport.width, mapped.viewport.height,
mapped.viewport.znear, mapped.viewport.zfar);
GXSetScissorRender(static_cast<u32>(std::max(mapped.scissor.x, 0)),
static_cast<u32>(std::max(mapped.scissor.y, 0)),
static_cast<u32>(std::max(mapped.scissor.width, 0)),
static_cast<u32>(std::max(mapped.scissor.height, 0)));
}
} // namespace
void GXSetViewportScissorRenderSafeArea(f32 aspect) {
const auto [targetWidth, targetHeight] = aurora::gfx::get_render_target_size();
if (targetWidth == 0 || targetHeight == 0 || !std::isfinite(aspect) || aspect <= 0.0f) {
return;
}
// Apply queued viewport changes before direct layout draws use the safe area.
aurora::gx::fifo::drain();
auto mapped = aurora::gx::map_logical_render_state();
const float targetAspect = static_cast<float>(targetWidth) / static_cast<float>(targetHeight);
float safeLeft = 0.0f;
float safeTop = 0.0f;
float safeWidth = static_cast<float>(targetWidth);
float safeHeight = static_cast<float>(targetHeight);
if (targetAspect > aspect) {
safeWidth = safeHeight * aspect;
safeLeft = (static_cast<float>(targetWidth) - safeWidth) * 0.5f;
} else if (targetAspect < aspect) {
safeHeight = safeWidth / aspect;
safeTop = (static_cast<float>(targetHeight) - safeHeight) * 0.5f;
}
const float scaleX = safeWidth / static_cast<float>(targetWidth);
const float scaleY = safeHeight / static_cast<float>(targetHeight);
mapped.viewport.left = safeLeft + mapped.viewport.left * scaleX;
mapped.viewport.top = safeTop + mapped.viewport.top * scaleY;
mapped.viewport.width *= scaleX;
mapped.viewport.height *= scaleY;
const float scissorLeft = safeLeft + static_cast<float>(mapped.scissor.x) * scaleX;
const float scissorTop = safeTop + static_cast<float>(mapped.scissor.y) * scaleY;
const float scissorRight =
safeLeft + static_cast<float>(mapped.scissor.x + mapped.scissor.width) * scaleX;
const float scissorBottom =
safeTop + static_cast<float>(mapped.scissor.y + mapped.scissor.height) * scaleY;
const int32_t left = std::clamp(static_cast<int32_t>(std::floor(scissorLeft)), 0,
static_cast<int32_t>(targetWidth));
const int32_t top = std::clamp(static_cast<int32_t>(std::floor(scissorTop)), 0,
static_cast<int32_t>(targetHeight));
const int32_t right = std::clamp(static_cast<int32_t>(std::ceil(scissorRight)), left,
static_cast<int32_t>(targetWidth));
const int32_t bottom = std::clamp(static_cast<int32_t>(std::ceil(scissorBottom)), top,
static_cast<int32_t>(targetHeight));
mapped.scissor = {left, top, right - left, bottom - top};
WriteMappedRenderState(mapped);
}
void GXRestoreViewportScissorRender() {
// Run queued GX draws before leaving the direct layout safe area.
aurora::gx::fifo::drain();
WriteMappedRenderState(aurora::gx::map_logical_render_state());
}
void GXSetTexCopySrcRender(u16 left, u16 top, u16 wd, u16 ht) {
aurora::gx::g_gxState.texCopySrc = {left, top, wd, ht};
aurora::gx::g_gxState.texCopySrcRenderSpace = true;
}
void GXCreateFrameBuffer(u32 width, u32 height) {
aurora::gx::fifo::drain();
aurora::gfx::begin_offscreen(width, height);
}
void GXRestoreFrameBuffer() {
aurora::gx::fifo::drain();
aurora::gfx::end_offscreen();
}