mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 06:00:25 +02:00
Add native steering-wheel vertex substitution to Aurora
The VR first-person camera will animate the local vehicle's steering wheel by handing Aurora a rotated copy of one of the vehicle's position arrays. A draw picks up the copy only when it binds that exact array with the local vehicle's position matrix (per-position ownership for indexed-matrix draws), so opponents sharing the asset keep the original vertices. The copy takes the same padded upload path as the original and never touches the shared array cache. Ported from heurazy's mario-kart-wii-VR-port (GPL-3.0-or-later).
This commit is contained in:
1 parent
709fa89ed9
commit
f3d7277ff3
7 files changed
+293
-2
No files matched your search
@@ -251,6 +251,20 @@ void aurora_set_stereo_scene_anchor(const float anchorFromScene[12]);
|
||||
// Producer-thread, per-frame metadata, consumed by the next end_frame call.
|
||||
// One (the default) keeps full-frame replay. Desktop rendering is unaffected.
|
||||
void aurora_set_stereo_local_player_count(uint32_t count);
|
||||
/**
|
||||
* VR native steering wheel: replacement position arrays for the local vehicle.
|
||||
*
|
||||
* GX (command processor) thread only, in order with the frame's draws: a host
|
||||
* that runs GX on its own thread must post these there. `source` is the host
|
||||
* pointer the game binds with GXSetArray; `replacement` is copied (at most 64
|
||||
* KiB) and applies solely to draws that bind that array with a position matrix
|
||||
* equal to `modelView` (row-major 3x4), so shared opponent models stay intact.
|
||||
* Clearing reports how many draws the previous set matched.
|
||||
*/
|
||||
void aurora_clear_native_wheel_vertices(void);
|
||||
void aurora_set_native_wheel_vertices(const void* source, const void* replacement, uint32_t size,
|
||||
const float* modelView);
|
||||
uint32_t aurora_native_wheel_draw_count(void);
|
||||
typedef void (*AuroraFrameWorkerWaitCallback)();
|
||||
// Called from the producer thread at bounded intervals while Aurora waits for
|
||||
// the asynchronous frame worker. The callback must not enter Aurora.
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Ported from heurazy's mario-kart-wii-VR-port (GPL-3.0-or-later).
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
|
||||
namespace aurora {
|
||||
// Every referenced position that changes must belong to the local body matrix.
|
||||
// Unchanged positions may use other joints in the same indexed draw (wing/engine).
|
||||
inline bool NativeWheelDrawMatches(std::span<const uint8_t> original,
|
||||
std::span<const uint8_t> replacement, uint32_t positionStride,
|
||||
std::span<const uint8_t> vertices, uint32_t vertexStride, uint32_t positionOffset,
|
||||
uint32_t indexBytes, uint16_t matchingMatrices) {
|
||||
if(!positionStride || !vertexStride || (indexBytes!=1 && indexBytes!=2) ||
|
||||
positionOffset>=vertexStride || indexBytes>vertexStride-positionOffset ||
|
||||
original.size()!=replacement.size() || vertices.size()%vertexStride || !matchingMatrices) return false;
|
||||
bool changed=false;
|
||||
for(size_t start=0;start<vertices.size();start+=vertexStride) {
|
||||
const auto* vertex=vertices.data()+start;
|
||||
const uint32_t index=indexBytes==1 ? vertex[positionOffset]
|
||||
: (uint32_t(vertex[positionOffset])<<8)|vertex[positionOffset+1];
|
||||
const size_t offset=size_t(index)*positionStride;
|
||||
if(offset>original.size() || positionStride>original.size()-offset) return false;
|
||||
if(std::memcmp(original.data()+offset,replacement.data()+offset,positionStride)==0) continue;
|
||||
const uint32_t matrix=vertex[0]/3u;
|
||||
if(vertex[0]%3u || matrix>=16 || !(matchingMatrices&(1u<<matrix))) return false;
|
||||
changed=true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,27 @@
|
||||
|
||||
#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() {
|
||||
if (!aurora::gx::nativeWheelArrays.empty()) aurora::gx::fifo::drain();
|
||||
aurora::gx::nativeWheelLastMatches.store(aurora::gx::nativeWheelMatches);
|
||||
aurora::gx::nativeWheelMatches = 0;
|
||||
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");
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "../gfx/texture_replacement.hpp"
|
||||
#include "dolphin/gx/GXAurora.h"
|
||||
#include "gx.hpp"
|
||||
#include "native_wheel.hpp"
|
||||
#include "gx_fmt.hpp"
|
||||
#include "pipeline.hpp"
|
||||
#include "shader_info.hpp"
|
||||
@@ -2354,8 +2355,10 @@ static bool handle_draw(u8 cmd, const u8* data, u32& pos, u32 size, bool bigEndi
|
||||
gfx::Range vertRange = push_draw_vertices(vertices, vtxCount, vtxSize);
|
||||
pos += totalVtxBytes;
|
||||
|
||||
// Try to merge with previous draw call
|
||||
if (!g_gxState.stateDirty && !(aurora::stereo_frame_provider_active() && g_gxState.projType == GX_ORTHOGRAPHIC))
|
||||
// Try to merge with previous draw call. A draw that binds a native-wheel source array is decided per draw
|
||||
// (native_wheel_array), so it never folds into a neighbour that resolved the array differently.
|
||||
if (!g_gxState.stateDirty && !(aurora::stereo_frame_provider_active() && g_gxState.projType == GX_ORTHOGRAPHIC) &&
|
||||
!native_wheel_source(g_gxState.arrays[GX_VA_POS].data))
|
||||
LIKELY {
|
||||
auto* lastDraw = gfx::get_last_draw_command<DrawData>();
|
||||
// Only if the previous draw call was a single instance draw (no lines/points handling)
|
||||
@@ -2419,6 +2422,27 @@ static void handle_draw_unmerged(GXPrimitive prim, GXVtxFmt fmt, u16 vtxCount, g
|
||||
}
|
||||
auto& array = g_gxState.arrays[i];
|
||||
const u32 uploadStride = padded_upload_stride(array.stride);
|
||||
if (i == GX_VA_POS && !nativeWheelArrays.empty())
|
||||
UNLIKELY {
|
||||
if (auto* nativeWheel = native_wheel_array(array, vertices, static_cast<u32>(vtxCount) * vtxStride, vtxStride,
|
||||
matrix_index_prefix_size(fmt))) {
|
||||
static unsigned nativeWheelDrawLogs = 0;
|
||||
if (nativeWheelDrawLogs++ < 4) Log.info("Native steering wheel: animated local vehicle vertex array");
|
||||
// Never populate the shared source's cache with the animated copy: later draws of the same asset must
|
||||
// still see the original vertices. The copy takes the same padded upload path as the original.
|
||||
if (nativeWheel->uploaded.size == 0 || nativeWheel->uploadedStride != uploadStride) {
|
||||
AttrArray animated{};
|
||||
animated.data = nativeWheel->bytes.data();
|
||||
animated.size = array.size;
|
||||
animated.stride = array.stride;
|
||||
animated.le = array.le;
|
||||
nativeWheel->uploaded = push_vertex_array(animated, uploadStride);
|
||||
nativeWheel->uploadedStride = uploadStride;
|
||||
}
|
||||
ranges.vaRanges[0] = nativeWheel->uploaded;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (array.cachedRange.size > 0 && array.cachedStride == uploadStride) {
|
||||
ranges.vaRanges[i - GX_VA_POS] = array.cachedRange;
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Ported from heurazy's mario-kart-wii-VR-port (GPL-3.0-or-later).
|
||||
//
|
||||
// The VR first-person camera animates the local vehicle's steering wheel by
|
||||
// handing Aurora a rotated copy of one of the vehicle's position arrays. The
|
||||
// copy applies only to a draw that binds that exact array *and* carries the
|
||||
// local vehicle's model-view matrix, so an opponent sharing the asset keeps
|
||||
// the original vertices. Everything here runs on the GX (command processor)
|
||||
// thread; the set/clear entry points are posted there by the runtime.
|
||||
#pragma once
|
||||
#include "gx.hpp"
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include <atomic>
|
||||
#include <aurora/native_wheel_match.hpp>
|
||||
namespace aurora::gx {
|
||||
struct NativeWheelArray {
|
||||
const void* source{};
|
||||
std::vector<uint8_t> bytes;
|
||||
std::array<float,12> modelView{};
|
||||
gfx::Range uploaded{};
|
||||
// Element stride of `uploaded`, which differs from the array's when the upload is padded.
|
||||
uint32_t uploadedStride=0;
|
||||
};
|
||||
inline std::vector<NativeWheelArray> nativeWheelArrays;
|
||||
inline uint32_t nativeWheelMatches=0;
|
||||
inline std::atomic<uint32_t> nativeWheelLastMatches{0};
|
||||
inline bool native_wheel_source(const void* source) {
|
||||
for(const auto& replacement:nativeWheelArrays) if(source==replacement.source) return true;
|
||||
return false;
|
||||
}
|
||||
inline NativeWheelArray* native_wheel_array(const AttrArray& array,const uint8_t* vertices,
|
||||
uint32_t vertexBytes,uint32_t vertexStride,uint32_t positionOffset) {
|
||||
const bool indexedMatrix=g_gxState.vtxDesc[GX_VA_PNMTXIDX]==GX_DIRECT;
|
||||
if(!indexedMatrix && g_gxState.currentPnMtx>=MaxPnMtx) return nullptr;
|
||||
for(auto& replacement:nativeWheelArrays) {
|
||||
if(array.data!=replacement.source || array.size>replacement.bytes.size()) continue;
|
||||
// A matching asset alone would also animate an opponent. Multi-joint
|
||||
// models need a per-position ownership check, not a blanket exclusion.
|
||||
uint16_t matching=0;
|
||||
for(uint32_t slot=0;slot<MaxPnMtx;++slot) {
|
||||
if(!indexedMatrix && slot!=g_gxState.currentPnMtx) continue;
|
||||
const auto* matrix=reinterpret_cast<const float*>(&g_gxState.pnMtx[slot].pos);
|
||||
bool matches=true;
|
||||
for(int i=0;i<12;++i) {
|
||||
uint32_t bits;std::memcpy(&bits,&matrix[i],4);
|
||||
if((bits&0x7f800000u)==0x7f800000u ||
|
||||
std::abs(matrix[i]-replacement.modelView[i])>(i%4==3?0.1f:0.002f)) { matches=false;break; }
|
||||
}
|
||||
if(matches) matching|=uint16_t(1u<<slot);
|
||||
}
|
||||
if(!matching) continue;
|
||||
if(indexedMatrix && !NativeWheelDrawMatches(
|
||||
{static_cast<const uint8_t*>(array.data),array.size},
|
||||
{replacement.bytes.data(),array.size},array.stride,
|
||||
{vertices,vertexBytes},vertexStride,positionOffset,
|
||||
g_gxState.vtxDesc[GX_VA_POS]==GX_INDEX8?1:2,matching)) continue;
|
||||
++nativeWheelMatches;return &replacement;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ if (AURORA_ENABLE_GX)
|
||||
stereo_replay_test.cpp
|
||||
stereo_interpolation_test.cpp
|
||||
stereo_mirror_test.cpp
|
||||
native_wheel_test.cpp
|
||||
texture_bind_group_cache_key_test.cpp
|
||||
../lib/gfx/efb_ram_encoder.cpp
|
||||
# GX API implementations (encoders)
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Native steering wheel: indexed-matrix ownership and array matching. The
|
||||
// ownership cases come from heurazy's mario-kart-wii-VR-port test set.
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "gx/native_wheel.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
// Three positions, 12 bytes each. Position 1 is on the wheel; 0 and 2 belong to
|
||||
// the body/wing.
|
||||
struct OwnershipFixture {
|
||||
std::array<uint8_t, 36> original{};
|
||||
std::array<uint8_t, 36> replacement{};
|
||||
// PN matrix byte, texture matrix byte, big-endian position index.
|
||||
std::array<uint8_t, 12> vertices{0, 30, 0, 0, 0, 30, 0, 1, 3, 30, 0, 2};
|
||||
OwnershipFixture() { replacement[12] = 7; }
|
||||
bool matches(uint16_t mask) const {
|
||||
return aurora::NativeWheelDrawMatches(original, replacement, 12, vertices, 4, 2, 2, mask);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(NativeWheelMatch, MixedBodyAndWingDrawAcceptsWheelPositionsOnLocalBody) {
|
||||
OwnershipFixture f;
|
||||
EXPECT_TRUE(f.matches(1));
|
||||
}
|
||||
|
||||
TEST(NativeWheelMatch, UnrelatedWingJointCannotAnimateWheel) {
|
||||
OwnershipFixture f;
|
||||
EXPECT_FALSE(f.matches(2));
|
||||
}
|
||||
|
||||
TEST(NativeWheelMatch, OpponentWithoutLocalMatrixIsRejected) {
|
||||
OwnershipFixture f;
|
||||
EXPECT_FALSE(f.matches(0));
|
||||
}
|
||||
|
||||
TEST(NativeWheelMatch, WheelPositionUnderAnotherMatrixIsRejected) {
|
||||
OwnershipFixture f;
|
||||
f.vertices[4] = 6;
|
||||
EXPECT_FALSE(f.matches(1));
|
||||
EXPECT_TRUE(f.matches(4)) << "local body can occupy a different palette slot";
|
||||
}
|
||||
|
||||
TEST(NativeWheelMatch, MalformedMatrixSelectorRejected) {
|
||||
OwnershipFixture f;
|
||||
f.vertices[4] = 1;
|
||||
EXPECT_FALSE(f.matches(1));
|
||||
}
|
||||
|
||||
TEST(NativeWheelMatch, OutOfRangePositionIndexRejected) {
|
||||
OwnershipFixture f;
|
||||
f.vertices[11] = 3;
|
||||
EXPECT_FALSE(f.matches(1));
|
||||
}
|
||||
|
||||
TEST(NativeWheelMatch, SelectionTouchingAnotherJointCannotDeformIt) {
|
||||
OwnershipFixture f;
|
||||
f.replacement[24] = 1;
|
||||
EXPECT_FALSE(f.matches(1));
|
||||
}
|
||||
|
||||
TEST(NativeWheelMatch, EightBitIndicesAndLayoutValidation) {
|
||||
OwnershipFixture f;
|
||||
std::array<uint8_t, 4> indexed8{0, 1, 3, 2};
|
||||
EXPECT_TRUE(aurora::NativeWheelDrawMatches(f.original, f.replacement, 12, indexed8, 2, 1, 1, 1));
|
||||
EXPECT_FALSE(aurora::NativeWheelDrawMatches(f.original, f.replacement, 12, indexed8, 2, 2, 1, 1))
|
||||
<< "invalid vertex layout rejected";
|
||||
EXPECT_FALSE(aurora::NativeWheelDrawMatches(f.original, f.original, 12, indexed8, 2, 1, 1, 1))
|
||||
<< "unchanged positions are not counted as animation";
|
||||
}
|
||||
|
||||
// Draw-time matching against the GX position-matrix palette.
|
||||
class NativeWheelArrayTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
aurora::gx::g_gxState = {};
|
||||
aurora::gx::nativeWheelArrays.clear();
|
||||
aurora::gx::nativeWheelMatches = 0;
|
||||
aurora::gx::NativeWheelArray replacement;
|
||||
replacement.source = source.data();
|
||||
replacement.bytes.assign(36, 0);
|
||||
replacement.bytes[12] = 1;
|
||||
aurora::gx::nativeWheelArrays.push_back(replacement);
|
||||
array.data = source.data();
|
||||
array.size = 36;
|
||||
array.stride = 12;
|
||||
}
|
||||
void TearDown() override {
|
||||
aurora::gx::g_gxState = {};
|
||||
aurora::gx::nativeWheelArrays.clear();
|
||||
}
|
||||
static float* pos(uint32_t slot) { return reinterpret_cast<float*>(&aurora::gx::g_gxState.pnMtx[slot].pos); }
|
||||
std::array<uint8_t, 36> source{};
|
||||
aurora::gx::AttrArray array{};
|
||||
};
|
||||
|
||||
TEST_F(NativeWheelArrayTest, MatchesLocalVehicleMatrixOnly) {
|
||||
EXPECT_NE(aurora::gx::native_wheel_array(array, nullptr, 0, 0, 0), nullptr);
|
||||
EXPECT_EQ(aurora::gx::nativeWheelMatches, 1u);
|
||||
// Same asset drawn by an opponent: a translated matrix.
|
||||
pos(0)[3] = 100;
|
||||
EXPECT_EQ(aurora::gx::native_wheel_array(array, nullptr, 0, 0, 0), nullptr);
|
||||
EXPECT_EQ(aurora::gx::nativeWheelMatches, 1u);
|
||||
}
|
||||
|
||||
TEST_F(NativeWheelArrayTest, UnboundSourceIsNotAWheelArray) {
|
||||
std::array<uint8_t, 36> other{};
|
||||
aurora::gx::AttrArray unrelated = array;
|
||||
unrelated.data = other.data();
|
||||
EXPECT_EQ(aurora::gx::native_wheel_array(unrelated, nullptr, 0, 0, 0), nullptr);
|
||||
EXPECT_TRUE(aurora::gx::native_wheel_source(source.data()));
|
||||
EXPECT_FALSE(aurora::gx::native_wheel_source(other.data()));
|
||||
}
|
||||
|
||||
TEST_F(NativeWheelArrayTest, MultiJointBodyChecksPerPositionOwnership) {
|
||||
// Multi-joint body with an independently animated wing, as in Mario's
|
||||
// mb/mc kart models. Only the wheel position uses the matching body slot.
|
||||
aurora::gx::g_gxState.vtxDesc[GX_VA_PNMTXIDX] = GX_DIRECT;
|
||||
aurora::gx::g_gxState.vtxDesc[GX_VA_POS] = GX_INDEX16;
|
||||
for (uint32_t slot = 0; slot < aurora::gx::MaxPnMtx; ++slot) pos(slot)[3] = 100;
|
||||
pos(2)[3] = 0;
|
||||
uint8_t vertices[]{6, 0, 1, 3, 0, 2};
|
||||
EXPECT_NE(aurora::gx::native_wheel_array(array, vertices, sizeof(vertices), 3, 1), nullptr);
|
||||
vertices[0] = 0; // opponent wheel while an unrelated palette slot still matches
|
||||
EXPECT_EQ(aurora::gx::native_wheel_array(array, vertices, sizeof(vertices), 3, 1), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(NativeWheelArrayTest, NonFiniteMatrixNeverMatches) {
|
||||
pos(0)[0] = __builtin_nanf("");
|
||||
EXPECT_EQ(aurora::gx::native_wheel_array(array, nullptr, 0, 0, 0), nullptr);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in new issue
Block a user