mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-10-21 13:54:55 -04:00
451 lines
18 KiB
C++
451 lines
18 KiB
C++
// gx_vertex.cpp - Vertex Descriptor and Attribute Functions
|
|
#include "gx_internal.h"
|
|
#include "gx_stream_common.h"
|
|
#include "fiber_manager.h"
|
|
#include "guest_interrupt_context.h"
|
|
#include "runtime_log.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
|
|
namespace {
|
|
uint32_t CanonicalVtxAttr(uint32_t attr) {
|
|
return attr == GX_VA_NBT ? GX_VA_NRM : attr;
|
|
}
|
|
|
|
// Array bases as last set through GXSetArray on the game thread.
|
|
std::array<std::array<uint32_t, 2>, 26> g_apiVtxArrays{};
|
|
|
|
// There is deliberately no indexed-aurora path here. The immediate begin path
|
|
// streams vertex data incrementally, and aurora's indexed-array upload must
|
|
// precede the draw carrying the max-index bounds, which cannot be known without
|
|
// buffering the whole primitive. Indexed attributes are therefore expanded into
|
|
// the packed direct stream below (GX_INDEX8/16 -> GX_DIRECT).
|
|
void ApplyAuroraVtxStateForBegin(GXVtxFmt fmt) {
|
|
GxStream::PublishAuroraVtxState(fmt,
|
|
GxStream::AuroraVtxPublishOptions{
|
|
/*includeNbt=*/false,
|
|
/*fmtLoopFirst=*/static_cast<int>(GX_VA_POS),
|
|
/*fmtLoopLast=*/static_cast<int>(GX_VA_TEX7)});
|
|
}
|
|
}
|
|
|
|
bool ApiVtxArray(uint32_t attr, uint32_t& base, uint32_t& stride) {
|
|
if (attr >= 26) return false;
|
|
base = g_apiVtxArrays[attr][0];
|
|
stride = g_apiVtxArrays[attr][1];
|
|
return base != 0 && stride != 0;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Vertex Descriptor
|
|
// ============================================================================
|
|
|
|
extern "C" void GX__ClearVtxDesc_8016dc34() {
|
|
GX_DEFER_CALL0(GX__ClearVtxDesc_8016dc34);
|
|
bool changed = false;
|
|
for(int i=0; i<26; ++i){
|
|
changed |= g_hleGxState.vtxDesc[i] != GX_NONE;
|
|
g_hleGxState.vtxDesc[i]=GX_NONE;
|
|
}
|
|
if (changed) g_hleGxState.InvalidateVtxLayoutHash();
|
|
// GXClearVtxDesc resets descriptors only; array base/stride state persists.
|
|
GXClearVtxDesc();
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(8016dc34, GX__ClearVtxDesc_8016dc34, (), ());
|
|
|
|
extern "C" void GX__SetVtxDesc_8016d3a4(uint32_t a, uint32_t t) {
|
|
GX_DEFER_CALL(GX__SetVtxDesc_8016d3a4, a, t);
|
|
const uint32_t attr = CanonicalVtxAttr(a);
|
|
if(attr>=26||attr==GX_VA_NULL) return;
|
|
const GXAttrType oldType = g_hleGxState.vtxDesc[attr];
|
|
g_hleGxState.vtxDesc[attr]=(GXAttrType)t;
|
|
if (a == GX_VA_NBT) {
|
|
g_hleGxState.vtxDesc[GX_VA_NBT] = GX_NONE;
|
|
}
|
|
if (g_hleGxState.vtxDesc[attr] != oldType) g_hleGxState.InvalidateVtxLayoutHash();
|
|
if(IsMatrixIndexAttr((GXAttr)attr)) return;
|
|
GXSetVtxDesc((GXAttr)a, (t==GX_INDEX8||t==GX_INDEX16)?GX_DIRECT:(GXAttrType)t);
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(8016d3a4, GX__SetVtxDesc_8016d3a4, (uint32_t a, uint32_t t), (a, t));
|
|
|
|
extern "C" void GX__SetVtxDescv_8016d608(uint32_t la) {
|
|
if(!la) return; uint32_t p=la;
|
|
std::vector<std::array<uint32_t, 2>> list;
|
|
while(true){
|
|
uint32_t a=Memory::Read32(p);
|
|
if(a==0xFFu) break;
|
|
list.push_back({a, Memory::Read32(p+4)});
|
|
p+=8;
|
|
}
|
|
GxWorker::Run([list = std::move(list)] {
|
|
for (const auto& [a, t] : list) GX__SetVtxDesc_8016d3a4(a, t);
|
|
});
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(8016d608, GX__SetVtxDescv_8016d608, (uint32_t la), (la));
|
|
|
|
// Display lists can change the vertex layout, so answer from the state the worker has applied.
|
|
extern "C" void GX__GetVtxDesc_8016d9f0(uint32_t a, uint32_t tp) {
|
|
GX_SYNC_INLINE();
|
|
GXAttrType type = GX_NONE;
|
|
const uint32_t attr = CanonicalVtxAttr(a);
|
|
if (attr < GX_VA_MAX_ATTR && attr != GX_VA_NULL) {
|
|
type = g_hleGxState.vtxDesc[attr];
|
|
}
|
|
if (tp) {
|
|
Memory::Write32(tp, static_cast<uint32_t>(type));
|
|
}
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(8016d9f0, GX__GetVtxDesc_8016d9f0, (uint32_t a, uint32_t tp), (a, tp));
|
|
|
|
extern "C" void GX__GetVtxDescv_8016dba4(uint32_t la) {
|
|
if (!la) {
|
|
return;
|
|
}
|
|
GX_SYNC_INLINE();
|
|
|
|
uint32_t p = la;
|
|
for (uint32_t a = GX_VA_PNMTXIDX; a <= GX_VA_TEX7; ++a) {
|
|
Memory::Write32(p, a);
|
|
Memory::Write32(p + 4, static_cast<uint32_t>(g_hleGxState.vtxDesc[a]));
|
|
p += 8;
|
|
}
|
|
Memory::Write32(p, GX_VA_NBT);
|
|
Memory::Write32(p + 4, static_cast<uint32_t>(g_hleGxState.vtxDesc[GX_VA_NRM]));
|
|
p += 8;
|
|
Memory::Write32(p, GX_VA_NULL);
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(8016dba4, GX__GetVtxDescv_8016dba4, (uint32_t la), (la));
|
|
|
|
// ============================================================================
|
|
// Vertex Attribute Format
|
|
// ============================================================================
|
|
|
|
extern "C" void GX__SetVtxAttrFmt_8016dc68(uint32_t vf, uint32_t a, uint32_t c, uint32_t t, uint32_t fr) {
|
|
GX_DEFER_CALL(GX__SetVtxAttrFmt_8016dc68, vf, a, c, t, fr);
|
|
const uint32_t attr = CanonicalVtxAttr(a);
|
|
if(vf<8&&attr<26){
|
|
const VtxAttrFmt oldFmt = g_hleGxState.vtxAttrFmt[vf][attr];
|
|
g_hleGxState.vtxAttrFmt[vf][attr].cnt=(GXCompCnt)c;
|
|
g_hleGxState.vtxAttrFmt[vf][attr].type=(GXCompType)t;
|
|
g_hleGxState.vtxAttrFmt[vf][attr].frac=(u8)fr;
|
|
if (a == GX_VA_NBT) {
|
|
g_hleGxState.vtxAttrFmt[vf][GX_VA_NBT] = {};
|
|
}
|
|
const auto& newFmt = g_hleGxState.vtxAttrFmt[vf][attr];
|
|
if (oldFmt.cnt != newFmt.cnt || oldFmt.type != newFmt.type || oldFmt.frac != newFmt.frac) {
|
|
g_hleGxState.InvalidateVtxLayoutHash();
|
|
}
|
|
}
|
|
if (vf >= GX_MAX_VTXFMT || a < GX_VA_POS || a >= GX_VA_MAX_ATTR) {
|
|
return;
|
|
}
|
|
GXSetVtxAttrFmt((GXVtxFmt)vf, (GXAttr)a, (GXCompCnt)c, (GXCompType)t, (u8)fr);
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(8016dc68, GX__SetVtxAttrFmt_8016dc68, (uint32_t vf, uint32_t a, uint32_t c, uint32_t t, uint32_t fr), (vf, a, c, t, fr));
|
|
|
|
extern "C" void GX__SetVtxAttrFmtv_8016de08(uint32_t vf, uint32_t la) {
|
|
if(!la) return; uint32_t p=la;
|
|
std::vector<std::array<uint32_t, 4>> list;
|
|
while(true){
|
|
uint32_t a=Memory::Read32(p);
|
|
if(a==0xFFu) break;
|
|
list.push_back({a, Memory::Read32(p+4), Memory::Read32(p+8), Memory::Read32(p+12)&0xFFu});
|
|
p+=16;
|
|
}
|
|
GxWorker::Run([vf, list = std::move(list)] {
|
|
for (const auto& [a, c, t, fr] : list) GX__SetVtxAttrFmt_8016dc68(vf, a, c, t, fr);
|
|
});
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(8016de08, GX__SetVtxAttrFmtv_8016de08, (uint32_t vf, uint32_t la), (vf, la));
|
|
|
|
extern "C" void GX__GetVtxAttrFmt_8016e04c(uint32_t vf, uint32_t a, uint32_t cp, uint32_t tp, uint32_t fp) {
|
|
GX_SYNC_INLINE();
|
|
VtxAttrFmt fmt{};
|
|
const uint32_t attr = CanonicalVtxAttr(a);
|
|
if (vf < 8 && attr < GX_VA_MAX_ATTR && attr != GX_VA_NULL) {
|
|
fmt = g_hleGxState.vtxAttrFmt[vf][attr];
|
|
}
|
|
if (cp) {
|
|
Memory::Write32(cp, static_cast<uint32_t>(fmt.cnt));
|
|
}
|
|
if (tp) {
|
|
Memory::Write32(tp, static_cast<uint32_t>(fmt.type));
|
|
}
|
|
if (fp) {
|
|
Memory::Write8(fp, fmt.frac);
|
|
}
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(8016e04c, GX__GetVtxAttrFmt_8016e04c, (uint32_t vf, uint32_t a, uint32_t cp, uint32_t tp, uint32_t fp), (vf, a, cp, tp, fp));
|
|
|
|
extern "C" void GX__GetVtxAttrFmtv_8016e2b8(uint32_t vf, uint32_t la) {
|
|
if (!la) {
|
|
return;
|
|
}
|
|
GX_SYNC_INLINE();
|
|
|
|
uint32_t p = la;
|
|
for (uint32_t a = GX_VA_POS; a <= GX_VA_TEX7; ++a) {
|
|
VtxAttrFmt fmt{};
|
|
if (vf < 8) {
|
|
fmt = g_hleGxState.vtxAttrFmt[vf][a];
|
|
}
|
|
Memory::Write32(p, a);
|
|
Memory::Write32(p + 4, static_cast<uint32_t>(fmt.cnt));
|
|
Memory::Write32(p + 8, static_cast<uint32_t>(fmt.type));
|
|
Memory::Write8(p + 12, fmt.frac);
|
|
p += 16;
|
|
}
|
|
Memory::Write32(p, GX_VA_NULL);
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(8016e2b8, GX__GetVtxAttrFmtv_8016e2b8, (uint32_t vf, uint32_t la), (vf, la));
|
|
|
|
// ============================================================================
|
|
// Vertex Arrays
|
|
// ============================================================================
|
|
|
|
extern "C" void GX__SetArray_8016e32c(uint32_t a, uint32_t ba, uint32_t str) {
|
|
if (!GxWorker::OnWorker()) {
|
|
const uint32_t attr = CanonicalVtxAttr(a);
|
|
if (attr < 26) g_apiVtxArrays[attr] = {ba, str};
|
|
}
|
|
GX_DEFER_CALL(GX__SetArray_8016e32c, a, ba, str);
|
|
const uint32_t attr = CanonicalVtxAttr(a);
|
|
if(attr<26){ g_hleGxState.vtxArray[attr].base=ba; g_hleGxState.vtxArray[attr].stride=str; }
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(8016e32c, GX__SetArray_8016e32c, (uint32_t a, uint32_t ba, uint32_t str), (a, ba, str));
|
|
|
|
// Switch-artifact entry point for the same SDK function; forwards rather than
|
|
// repeating the body.
|
|
extern "C" void GX__SetArray_8016e1c4(uint32_t a, uint32_t b, uint32_t s) { GX__SetArray_8016e32c(a, b, s); }
|
|
PPC_NATIVE_OVERRIDE_VOID(8016e1c4, GX__SetArray_8016e1c4, (uint32_t a, uint32_t b, uint32_t s), (a, b, s));
|
|
|
|
// ============================================================================
|
|
// Texture Coordinate Generation
|
|
// ============================================================================
|
|
|
|
extern "C" void GX__SetNumTexGens_8016e5a4(uint32_t n) {
|
|
GX_DEFER_CALL(GX__SetNumTexGens_8016e5a4, n);
|
|
GXSetNumTexGens((u8)n);
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(8016e5a4, GX__SetNumTexGens_8016e5a4, (uint32_t n), (n));
|
|
|
|
extern "C" void GX__SetTexCoordGen2_8016e37c(uint32_t dc, uint32_t f, uint32_t sp, uint32_t m, uint32_t n, uint32_t pm) {
|
|
GX_DEFER_CALL(GX__SetTexCoordGen2_8016e37c, dc, f, sp, m, n, pm);
|
|
if (sp >= static_cast<uint32_t>(GX_MAX_TEXGENSRC)) {
|
|
uint32_t pc = 0;
|
|
uint32_t lr = 0;
|
|
if (auto* cpu = TryGetCpuContext()) {
|
|
pc = cpu->pc;
|
|
lr = cpu->lr;
|
|
}
|
|
RT_LOGF(RT_TAG_GX,
|
|
"GXSetTexCoordGen2 invalid src=%u dst=%u type=%u mtx=%u norm=%u post=%u PC=0x%08X LR=0x%08X\n",
|
|
sp, dc, f, m, n, pm, pc, lr);
|
|
}
|
|
GXSetTexCoordGen2((GXTexCoordID)dc, (GXTexGenType)f, (GXTexGenSrc)sp, m, (GXBool)n, pm);
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(8016e37c, GX__SetTexCoordGen2_8016e37c, (uint32_t dc, uint32_t f, uint32_t sp, uint32_t m, uint32_t n, uint32_t pm), (dc, f, sp, m, n, pm));
|
|
|
|
extern "C" void GX__EnableTexOffsets_8016f37c(uint32_t coord, uint32_t lineEnable, uint32_t pointEnable) {
|
|
GX_DEFER_CALL(GX__EnableTexOffsets_8016f37c, coord, lineEnable, pointEnable);
|
|
GXEnableTexOffsets(static_cast<GXTexCoordID>(coord),
|
|
lineEnable ? GX_TRUE : GX_FALSE,
|
|
pointEnable ? GX_TRUE : GX_FALSE);
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(8016f37c, GX__EnableTexOffsets_8016f37c, (uint32_t coord, uint32_t lineEnable, uint32_t pointEnable), (coord, lineEnable, pointEnable));
|
|
|
|
extern "C" void GX__SetLineWidth_8016f314(uint32_t width, uint32_t texOffsets) {
|
|
GX_DEFER_CALL(GX__SetLineWidth_8016f314, width, texOffsets);
|
|
GXSetLineWidth(static_cast<u8>(width), static_cast<GXTexOffset>(texOffsets));
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(8016f314, GX__SetLineWidth_8016f314, (uint32_t width, uint32_t texOffsets), (width, texOffsets));
|
|
|
|
extern "C" void GX__SetPointSize_8016f348(uint32_t pointSize, uint32_t texOffsets) {
|
|
GX_DEFER_CALL(GX__SetPointSize_8016f348, pointSize, texOffsets);
|
|
GXSetPointSize(static_cast<u8>(pointSize), static_cast<GXTexOffset>(texOffsets));
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(8016f348, GX__SetPointSize_8016f348, (uint32_t pointSize, uint32_t texOffsets), (pointSize, texOffsets));
|
|
|
|
// ============================================================================
|
|
// Begin/End Drawing
|
|
// ============================================================================
|
|
|
|
namespace {
|
|
thread_local bool g_inGxBeginOrFifo = false;
|
|
thread_local std::chrono::steady_clock::time_point g_nextDeferredTimingPoll{};
|
|
|
|
void ServiceDeferredTimingDuringGxWork() {
|
|
if (g_inGxBeginOrFifo) {
|
|
return;
|
|
}
|
|
|
|
const auto now = std::chrono::steady_clock::now();
|
|
if (now < g_nextDeferredTimingPoll) {
|
|
return;
|
|
}
|
|
g_nextDeferredTimingPoll = now + std::chrono::milliseconds(1);
|
|
|
|
g_inGxBeginOrFifo = true;
|
|
try {
|
|
if (Fiber::GuestFiberManager::IsInitialized()) {
|
|
// VI callbacks are what advance EGG::AsyncDisplay on NTSC/PAL60;
|
|
// PAL50 uses an OS alarm. Keep both clocks moving while a slow
|
|
// host frame is still being submitted, but defer fiber switches
|
|
// and Aurora work until the native GX call has unwound.
|
|
VI_HLE_ProcessRetracesDeferred(8);
|
|
OS_HLE_ProcessAlarmsDeferred(8);
|
|
} else if (TryGetCpuContext() != nullptr) {
|
|
// Same interrupt isolation as the deferred paths above: this GX call
|
|
// is mid-execution of a translated function, so the retrace
|
|
// callbacks must not publish their registers into its context.
|
|
GuestInterruptCallbackContext interrupt;
|
|
VI_HLE_PollRetrace(interrupt.get());
|
|
}
|
|
} catch (...) {
|
|
g_inGxBeginOrFifo = false;
|
|
throw;
|
|
}
|
|
g_inGxBeginOrFifo = false;
|
|
}
|
|
}
|
|
|
|
static void GxBeginImpl(uint32_t t, uint32_t vf, uint32_t nv);
|
|
|
|
extern "C" void GX__Begin_8016f0f0(uint32_t t, uint32_t vf, uint32_t nv) {
|
|
if(IsDisplayListActive()){
|
|
WriteDisplayListData((u8)(t|vf), 1);
|
|
WriteDisplayListData((u16)nv, 2);
|
|
return;
|
|
}
|
|
ServiceDeferredTimingDuringGxWork();
|
|
GX_DEFER_CALL(GxBeginImpl, t, vf, nv);
|
|
GxBeginImpl(t, vf, nv);
|
|
}
|
|
|
|
static void GxBeginImpl(uint32_t t, uint32_t vf, uint32_t nv) {
|
|
ApplyAuroraVtxStateForBegin(static_cast<GXVtxFmt>(vf));
|
|
g_hleGxState.currentVtxFmt=(GXVtxFmt)vf; g_hleGxState.currentPrim=(GXPrimitive)t;
|
|
g_hleGxState.vertsRemaining=nv; g_hleGxState.inBegin=true; g_hleGxState.auroraBeginCalled=false;
|
|
g_hleGxState.fifoReadOffset = 0;
|
|
g_hleGxState.fifoByteCount=0; g_hleGxState.ResetVertex();
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(8016f0f0, GX__Begin_8016f0f0, (uint32_t t, uint32_t vf, uint32_t nv), (t, vf, nv));
|
|
|
|
extern "C" void GX__End_80044b30() {
|
|
GX_DEFER_CALL0(GX__End_80044b30); g_hleGxState.inBegin=false; GXEnd(); }
|
|
PPC_NATIVE_OVERRIDE_VOID(80044b30, GX__End_80044b30, (), ());
|
|
|
|
extern "C" void GX__End_80048c30() { GX__End_80044b30(); }
|
|
PPC_NATIVE_OVERRIDE_VOID(80048c30, GX__End_80048c30, (), ());
|
|
|
|
extern "C" void GX__DrawSphere_80172a30(uint32_t numMajor, uint32_t numMinor) {
|
|
if (GxWorker::ShouldDefer()) {
|
|
EnsureAuroraFrameActive();
|
|
GxWorker::Submit([=] { GX__DrawSphere_80172a30(numMajor, numMinor); });
|
|
return;
|
|
}
|
|
constexpr uint32_t kAttrCount = 26;
|
|
constexpr float kSphereRadius = 1.0f;
|
|
constexpr float kPi = 3.14159265358979323846f;
|
|
constexpr float kTwoPi = kPi * 2.0f;
|
|
|
|
std::array<GXAttrType, kAttrCount> savedVtxDesc{};
|
|
std::array<VtxAttrFmt, kAttrCount> savedFmt3{};
|
|
for (uint32_t i = 0; i < kAttrCount; ++i) {
|
|
savedVtxDesc[i] = g_hleGxState.vtxDesc[i];
|
|
savedFmt3[i] = g_hleGxState.vtxAttrFmt[GX_VTXFMT3][i];
|
|
}
|
|
|
|
const bool hadTex0 = savedVtxDesc[GX_VA_TEX0] != GX_NONE;
|
|
|
|
bool drawOpen = false;
|
|
try {
|
|
EnsureAuroraFrameActive();
|
|
|
|
for (uint32_t i = 0; i < kAttrCount; ++i) {
|
|
g_hleGxState.vtxDesc[i] = GX_NONE;
|
|
}
|
|
g_hleGxState.InvalidateVtxLayoutHash();
|
|
GXClearVtxDesc();
|
|
|
|
GX__SetVtxDesc_8016d3a4(GX_VA_POS, GX_DIRECT);
|
|
GX__SetVtxDesc_8016d3a4(GX_VA_NRM, GX_DIRECT);
|
|
GX__SetVtxAttrFmt_8016dc68(GX_VTXFMT3, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
|
|
GX__SetVtxAttrFmt_8016dc68(GX_VTXFMT3, GX_VA_NRM, GX_NRM_XYZ, GX_F32, 0);
|
|
if (hadTex0) {
|
|
GX__SetVtxDesc_8016d3a4(GX_VA_TEX0, GX_DIRECT);
|
|
GX__SetVtxAttrFmt_8016dc68(GX_VTXFMT3, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
|
|
}
|
|
|
|
const float majorStep = kPi / static_cast<float>(numMajor);
|
|
const float minorStep = kTwoPi / static_cast<float>(numMinor);
|
|
const float invRadius = 1.0f / kSphereRadius;
|
|
|
|
for (uint32_t major = 0; major < numMajor; ++major) {
|
|
const float major0 = static_cast<float>(major) * majorStep;
|
|
const float major1 = major0 + majorStep;
|
|
|
|
const float ring0 = kSphereRadius * std::sin(major0);
|
|
const float ring1 = kSphereRadius * std::sin(major1);
|
|
const float z0 = kSphereRadius * std::cos(major0);
|
|
const float z1 = kSphereRadius * std::cos(major1);
|
|
|
|
const uint16_t vertexCount = static_cast<uint16_t>(((numMinor + 1u) * 2u) & 0xFFFEu);
|
|
GXBegin(GX_TRIANGLESTRIP, GX_VTXFMT3, vertexCount);
|
|
drawOpen = true;
|
|
|
|
const float nz0 = z0 * invRadius;
|
|
const float nz1 = z1 * invRadius;
|
|
|
|
for (uint32_t minor = 0; minor <= numMinor; ++minor) {
|
|
const float minorAngle = static_cast<float>(minor) * minorStep;
|
|
const float c = std::cos(minorAngle);
|
|
const float s = std::sin(minorAngle);
|
|
|
|
const float x1 = c * ring1;
|
|
const float y1 = s * ring1;
|
|
GXPosition3f32(x1, y1, z1);
|
|
GXNormal3f32(x1 * invRadius, y1 * invRadius, nz1);
|
|
if (hadTex0) {
|
|
GXTexCoord2f32(static_cast<float>(minor) / static_cast<float>(numMinor),
|
|
static_cast<float>(major + 1u) / static_cast<float>(numMajor));
|
|
}
|
|
|
|
const float x0 = c * ring0;
|
|
const float y0 = s * ring0;
|
|
GXPosition3f32(x0, y0, z0);
|
|
GXNormal3f32(x0 * invRadius, y0 * invRadius, nz0);
|
|
if (hadTex0) {
|
|
GXTexCoord2f32(static_cast<float>(minor) / static_cast<float>(numMinor),
|
|
static_cast<float>(major) / static_cast<float>(numMajor));
|
|
}
|
|
}
|
|
GXEnd();
|
|
drawOpen = false;
|
|
}
|
|
} catch (...) {
|
|
if (drawOpen) {
|
|
GXEnd();
|
|
drawOpen = false;
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < kAttrCount; ++i) {
|
|
g_hleGxState.vtxDesc[i] = GX_NONE;
|
|
}
|
|
g_hleGxState.InvalidateVtxLayoutHash();
|
|
GXClearVtxDesc();
|
|
for (uint32_t attr = 0; attr < kAttrCount; ++attr) {
|
|
const GXAttrType type = savedVtxDesc[attr];
|
|
if (type != GX_NONE) {
|
|
GX__SetVtxDesc_8016d3a4(attr, static_cast<uint32_t>(type));
|
|
}
|
|
}
|
|
for (uint32_t attr = GX_VA_POS; attr <= GX_VA_TEX7; ++attr) {
|
|
const VtxAttrFmt& fmt = savedFmt3[attr];
|
|
GX__SetVtxAttrFmt_8016dc68(GX_VTXFMT3, attr, static_cast<uint32_t>(fmt.cnt), static_cast<uint32_t>(fmt.type), fmt.frac);
|
|
}
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(80172a30, GX__DrawSphere_80172a30, (uint32_t numMajor, uint32_t numMinor), (numMajor, numMinor));
|