mirror of
https://github.com/encounter/aurora.git
synced 2026-07-09 18:19:33 -07:00
Add aurora::gx::dl reader & optimizer
This commit is contained in:
@@ -11,7 +11,9 @@ add_library(aurora_gx STATIC
|
||||
lib/gfx/texture_format.cpp
|
||||
lib/gfx/texture_convert.cpp
|
||||
lib/gfx/texture_replacement.cpp
|
||||
lib/gx/attr_fmt.cpp
|
||||
lib/gx/command_processor.cpp
|
||||
lib/gx/dl.cpp
|
||||
lib/gx/fifo.cpp
|
||||
lib/gx/gx.cpp
|
||||
lib/gx/pipeline.cpp
|
||||
|
||||
Vendored
+2
-2
@@ -308,8 +308,8 @@ if (NOT TARGET TracyClient)
|
||||
|
||||
FetchContent_Declare(
|
||||
tracy
|
||||
URL https://github.com/wolfpld/tracy/archive/a64b9a20294d59421a2f57aeca3c6383d8c48169.tar.gz
|
||||
URL_HASH SHA256=24d342b5127d7f659dc3cf94f24347b348cc736f25b17a691fd7f69541937658
|
||||
URL https://github.com/wolfpld/tracy/archive/6789e7d6f9a65ec98926b602097a33a9676d2606.tar.gz
|
||||
URL_HASH SHA256=ebfe4fb50d7c254901979355c80a7d4cd33624aa2ec0fe90b3b238153cd5d69b
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
|
||||
#include <dolphin/gx.h>
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace aurora::gx::dl {
|
||||
|
||||
struct VtxLayout {
|
||||
struct Attr {
|
||||
u8 offset = 0;
|
||||
u8 size = 0;
|
||||
GXAttrType type = GX_NONE;
|
||||
};
|
||||
Attr attrs[GX_VA_MAX_ATTR]{};
|
||||
u8 stride = 0;
|
||||
};
|
||||
|
||||
using VtxFmtLists = std::array<const GXVtxAttrFmtList*, GX_MAX_VTXFMT>;
|
||||
|
||||
struct DrawCmd {
|
||||
GXPrimitive prim;
|
||||
GXVtxFmt fmt;
|
||||
u16 vtxCount;
|
||||
const u8* vertices;
|
||||
const VtxLayout* layout;
|
||||
const u8* indices; // DrawIndexed only: native-endian u16s
|
||||
u32 indexCount;
|
||||
|
||||
const u8* vtx(u32 vtx) const { return vertices + vtx * layout->stride; }
|
||||
/** Read an indexed attribute (GX_INDEX8 / GX_INDEX16) or a GX_DIRECT MTXIDX. */
|
||||
u16 attr_idx(u32 vtx, GXAttr attr) const;
|
||||
/** Read one prebuilt index (DrawIndexed only). */
|
||||
u16 index(u32 i) const;
|
||||
};
|
||||
|
||||
struct Command {
|
||||
enum class Kind {
|
||||
Draw, // GX draw prim
|
||||
DrawIndexed, // GX_AURORA_DRAW_INDEXED
|
||||
Passthrough, // everything else
|
||||
};
|
||||
Kind kind;
|
||||
const u8* data; // full command bytes, including the opcode
|
||||
u32 size;
|
||||
DrawCmd draw; // if Draw or DrawIndexed
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses a big-endian GX display list command by command.
|
||||
*
|
||||
* Vertex layouts are computed per vertex format from the desc list.
|
||||
* GX_VA_*MTXIDX attributes are always one byte.
|
||||
* Any other GX_DIRECT attribute requires a GXVtxAttrFmtList for the referenced
|
||||
* GX_VTXFMT; if not present, the reader will return failure.
|
||||
*/
|
||||
class Reader {
|
||||
public:
|
||||
Reader(const u8* dl, u32 size, const GXVtxDescList* desc, const VtxFmtLists* fmts = nullptr);
|
||||
/**
|
||||
* Stride-only reader for callers that know the vertex stride but not the
|
||||
* attribute format. DrawCmd::attr_idx will always return 0.
|
||||
*/
|
||||
Reader(const u8* dl, u32 size, u8 stride);
|
||||
|
||||
std::optional<Command> next();
|
||||
bool failed() const { return mFailed; }
|
||||
u32 pos() const { return mPos; }
|
||||
|
||||
private:
|
||||
const u8* mData;
|
||||
u32 mSize;
|
||||
u32 mPos = 0;
|
||||
const GXVtxDescList* mDesc = nullptr;
|
||||
const VtxFmtLists* mFmts = nullptr;
|
||||
bool mFailed = false;
|
||||
std::optional<VtxLayout> mLayouts[GX_MAX_VTXFMT];
|
||||
bool mLayoutComputed[GX_MAX_VTXFMT] = {};
|
||||
|
||||
const VtxLayout* layout(GXVtxFmt fmt);
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
bool expand_triangles(GXPrimitive prim, u16 vtxCount, F&& f) {
|
||||
switch (prim) {
|
||||
case GX_TRIANGLES:
|
||||
if (vtxCount < 3 || vtxCount % 3 != 0) {
|
||||
return false;
|
||||
}
|
||||
for (u16 v = 0; v < vtxCount; v += 3) {
|
||||
f(v, static_cast<u16>(v + 1), static_cast<u16>(v + 2));
|
||||
}
|
||||
return true;
|
||||
case GX_TRIANGLESTRIP:
|
||||
if (vtxCount < 3) {
|
||||
return false;
|
||||
}
|
||||
for (u16 v = 2; v < vtxCount; ++v) {
|
||||
if ((v & 1) == 0) {
|
||||
f(static_cast<u16>(v - 2), static_cast<u16>(v - 1), v);
|
||||
} else {
|
||||
f(static_cast<u16>(v - 1), static_cast<u16>(v - 2), v);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case GX_TRIANGLEFAN:
|
||||
if (vtxCount < 3) {
|
||||
return false;
|
||||
}
|
||||
for (u16 v = 2; v < vtxCount; ++v) {
|
||||
f(0, static_cast<u16>(v - 1), v);
|
||||
}
|
||||
return true;
|
||||
case GX_QUADS:
|
||||
if (vtxCount < 4 || vtxCount % 4 != 0) {
|
||||
return false;
|
||||
}
|
||||
for (u16 v = 0; v < vtxCount; v += 4) {
|
||||
f(v, static_cast<u16>(v + 1), static_cast<u16>(v + 2));
|
||||
f(static_cast<u16>(v + 2), static_cast<u16>(v + 3), v);
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite a display list, merging adjacent triangulable draws into prebuilt GX_AURORA_DRAW_INDEXED commands.
|
||||
* State commands are passed through untouched, NOPs are dropped.
|
||||
* Returns nullopt if the display list contains anything unsupported.
|
||||
*/
|
||||
std::optional<std::vector<u8>> optimize(const u8* dl, u32 size, const GXVtxDescList* desc,
|
||||
const VtxFmtLists* fmts = nullptr);
|
||||
|
||||
} // namespace aurora::gx::dl
|
||||
@@ -142,7 +142,7 @@ inline static void GDWriteBPCmd(u32 regval) {
|
||||
}
|
||||
|
||||
inline static void GDWriteAuroraCmd(u16 subCommand) {
|
||||
GDWrite_u8(GX_LOAD_AURORA);
|
||||
GDWrite_u8(GX_AURORA);
|
||||
GDWrite_u16(subCommand);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,58 +8,58 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
//
|
||||
// Subcommands for GX_LOAD_AURORA.
|
||||
// Subcommands for GX_AURORA.
|
||||
//
|
||||
|
||||
/**
|
||||
* Sets the actual render viewport in native framebuffer coordinates.
|
||||
* Must be followed by six f32 values: left, top, width, height, nearz, farz.
|
||||
*/
|
||||
#define GX_LOAD_AURORA_VIEWPORT_RENDER 0x0001
|
||||
#define GX_AURORA_LOAD_VIEWPORT_RENDER 0x0001
|
||||
|
||||
/**
|
||||
* Sets the actual render scissor in native framebuffer coordinates.
|
||||
* Must be followed by four u32 values: left, top, width, height.
|
||||
*/
|
||||
#define GX_LOAD_AURORA_SCISSOR_RENDER 0x0002
|
||||
#define GX_AURORA_LOAD_SCISSOR_RENDER 0x0002
|
||||
|
||||
/**
|
||||
* Aurora equivalent of CP_REG_ARRAYBASE_ID: sets the base address and size of a vertex array.
|
||||
* This command must be followed by a 64-bit memory address, 32-bit size, and 1-byte little-endian flag.
|
||||
* The index of the vertex array is given by the lowest 4 bits of the command ID,
|
||||
* e.g. writing GX_LOAD_AURORA_ARRAYBASE + 5 will set the vertex array for the sixth vertex attribute.
|
||||
* e.g. writing GX_AURORA_LOAD_ARRAYBASE + 5 will set the vertex array for the sixth vertex attribute.
|
||||
* To set strides, use the normal CP_REG_ARRAYSTRIDE_ID register.
|
||||
*/
|
||||
#define GX_LOAD_AURORA_ARRAYBASE 0x0010
|
||||
#define GX_AURORA_LOAD_ARRAYBASE 0x0010
|
||||
|
||||
/**
|
||||
* Pushes a debug group to the backend graphics API. These may show in debugging tools such as RenderDoc.
|
||||
* Must be followed by a u16 string length and that many UTF-8 characters (no null terminator required).
|
||||
* It is considered an error to have unpopped debug groups at the end of the frame. They will be automatically cleared.
|
||||
*/
|
||||
#define GX_LOAD_AURORA_DEBUG_GROUP_PUSH 0x0020
|
||||
#define GX_AURORA_DEBUG_GROUP_PUSH 0x0020
|
||||
|
||||
/**
|
||||
* Pops a previously pushed debug group.
|
||||
* Followed by nothing.
|
||||
*/
|
||||
#define GX_LOAD_AURORA_DEBUG_GROUP_POP 0x0021
|
||||
#define GX_AURORA_DEBUG_GROUP_POP 0x0021
|
||||
|
||||
/**
|
||||
* Sends a debug marker to the backend graphics API.
|
||||
* Must be followed by a u16 string length and that many UTF-8 characters (no null terminator required).
|
||||
*/
|
||||
#define GX_LOAD_AURORA_DEBUG_MARKER_INSERT 0x0022
|
||||
#define GX_AURORA_DEBUG_MARKER_INSERT 0x0022
|
||||
|
||||
#define GX_LOAD_AURORA_TEXOBJ 0x0030
|
||||
#define GX_AURORA_LOAD_TEXOBJ 0x0030
|
||||
|
||||
#define GX_LOAD_AURORA_TLUT 0x0031
|
||||
#define GX_AURORA_LOAD_TLUT 0x0031
|
||||
|
||||
#define GX_LOAD_AURORA_DESTROY_TEXOBJ 0x0032
|
||||
#define GX_AURORA_DESTROY_TEXOBJ 0x0032
|
||||
|
||||
#define GX_LOAD_AURORA_DESTROY_TLUT 0x0033
|
||||
#define GX_AURORA_DESTROY_TLUT 0x0033
|
||||
|
||||
#define GX_LOAD_AURORA_DESTROY_COPY_TEX 0x0034
|
||||
#define GX_AURORA_DESTROY_COPY_TEX 0x0034
|
||||
|
||||
/**
|
||||
* Draw primitives with the vertex count derived from a byte length, as written by
|
||||
@@ -67,7 +67,16 @@ extern "C" {
|
||||
* a u32 vertex data byte length, then that many bytes of vertex data. The byte length
|
||||
* must be a whole multiple of the current vertex size or zero (no draw).
|
||||
*/
|
||||
#define GX_LOAD_AURORA_DRAW_SIZED 0x0040
|
||||
#define GX_AURORA_DRAW_SIZED 0x0040
|
||||
|
||||
/**
|
||||
* Draw pre-merged triangles with a prebuilt index buffer, as written by the display
|
||||
* list optimizer (aurora::gx::dl::optimize). Must be followed by a u8 draw opcode
|
||||
* (vtxfmt | GX_TRIANGLES), a u16 vertex count, a u32 index count, that many u16
|
||||
* indices, then vertex count * vertex size bytes of packed vertex data. Index data
|
||||
* is always host-endian regardless of stream endianness.
|
||||
*/
|
||||
#define GX_AURORA_DRAW_INDEXED 0x0041
|
||||
|
||||
#define GX2_SET_POLYGON_OFFSET 0x1000
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ extern "C" {
|
||||
* Custom Aurora commands that are followed by another 2-byte identifier.
|
||||
* See GXAurora.h for further documentation on these.
|
||||
*/
|
||||
#define GX_LOAD_AURORA 0x50
|
||||
#define GX_AURORA 0x50
|
||||
|
||||
#define GX_OPCODE_MASK 0xF8
|
||||
#define GX_VAT_MASK 0x07
|
||||
|
||||
@@ -18,15 +18,15 @@ static void GDWriteString(const char* label) {
|
||||
}
|
||||
|
||||
void GDPushDebugGroup(const char* label) {
|
||||
GDWriteAuroraCmd(GX_LOAD_AURORA_DEBUG_GROUP_PUSH);
|
||||
GDWriteAuroraCmd(GX_AURORA_DEBUG_GROUP_PUSH);
|
||||
GDWriteString(label);
|
||||
}
|
||||
|
||||
void GDPopDebugGroup() {
|
||||
GDWriteAuroraCmd(GX_LOAD_AURORA_DEBUG_GROUP_POP);
|
||||
GDWriteAuroraCmd(GX_AURORA_DEBUG_GROUP_POP);
|
||||
}
|
||||
|
||||
void GDInsertDebugMarker(const char* label) {
|
||||
GDWriteAuroraCmd(GX_LOAD_AURORA_DEBUG_MARKER_INSERT);
|
||||
GDWriteAuroraCmd(GX_AURORA_DEBUG_MARKER_INSERT);
|
||||
GDWriteString(label);
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ void GDSetArraySized(GXAttr attr, void* base_ptr, u32 size, u8 stride, bool le)
|
||||
|
||||
assert((cpAttr & ~0xF) == 0);
|
||||
|
||||
GDWriteAuroraCmd(cpAttr + GX_LOAD_AURORA_ARRAYBASE);
|
||||
GDWriteAuroraCmd(cpAttr + GX_AURORA_LOAD_ARRAYBASE);
|
||||
GDWrite_u64((u64)base_ptr);
|
||||
GDWrite_u32(size);
|
||||
GDWrite_u8(le ? 1 : 0);
|
||||
|
||||
@@ -22,14 +22,14 @@ static void GXWriteString(const char* label) {
|
||||
}
|
||||
|
||||
void GXPushDebugGroup(const char* label) {
|
||||
GX_WRITE_AURORA(GX_LOAD_AURORA_DEBUG_GROUP_PUSH);
|
||||
GX_WRITE_AURORA(GX_AURORA_DEBUG_GROUP_PUSH);
|
||||
GXWriteString(label);
|
||||
}
|
||||
|
||||
void GXPopDebugGroup() { GX_WRITE_AURORA(GX_LOAD_AURORA_DEBUG_GROUP_POP); }
|
||||
void GXPopDebugGroup() { GX_WRITE_AURORA(GX_AURORA_DEBUG_GROUP_POP); }
|
||||
|
||||
void GXInsertDebugMarker(const char* label) {
|
||||
GX_WRITE_AURORA(GX_LOAD_AURORA_DEBUG_MARKER_INSERT);
|
||||
GX_WRITE_AURORA(GX_AURORA_DEBUG_MARKER_INSERT);
|
||||
GXWriteString(label);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ void AuroraGetRenderSize(u32* width, u32* 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_AURORA(GX_AURORA_LOAD_VIEWPORT_RENDER);
|
||||
GX_WRITE_F32(left);
|
||||
GX_WRITE_F32(top);
|
||||
GX_WRITE_F32(wd);
|
||||
@@ -59,7 +59,7 @@ void GXSetViewportRender(f32 left, f32 top, f32 wd, f32 ht, f32 nearz, f32 farz)
|
||||
}
|
||||
|
||||
void GXSetScissorRender(u32 left, u32 top, u32 wd, u32 ht) {
|
||||
GX_WRITE_AURORA(GX_LOAD_AURORA_SCISSOR_RENDER);
|
||||
GX_WRITE_AURORA(GX_AURORA_LOAD_SCISSOR_RENDER);
|
||||
GX_WRITE_U32(left);
|
||||
GX_WRITE_U32(top);
|
||||
GX_WRITE_U32(wd);
|
||||
|
||||
@@ -6,7 +6,7 @@ extern "C" {
|
||||
void GXDestroyTexObj(GXTexObj* obj_) {
|
||||
auto* obj = reinterpret_cast<GXTexObj_*>(obj_);
|
||||
if (obj->texObjId != 0) {
|
||||
GX_WRITE_AURORA(GX_LOAD_AURORA_DESTROY_TEXOBJ);
|
||||
GX_WRITE_AURORA(GX_AURORA_DESTROY_TEXOBJ);
|
||||
GX_WRITE_U32(obj->texObjId);
|
||||
}
|
||||
obj->texObjId = 0;
|
||||
@@ -15,7 +15,7 @@ void GXDestroyTexObj(GXTexObj* obj_) {
|
||||
void GXDestroyTlutObj(GXTlutObj* obj_) {
|
||||
auto* obj = reinterpret_cast<GXTlutObj_*>(obj_);
|
||||
if (obj->tlutObjId != 0) {
|
||||
GX_WRITE_AURORA(GX_LOAD_AURORA_DESTROY_TLUT);
|
||||
GX_WRITE_AURORA(GX_AURORA_DESTROY_TLUT);
|
||||
GX_WRITE_U32(obj->tlutObjId);
|
||||
}
|
||||
obj->tlutObjId = 0;
|
||||
@@ -23,7 +23,7 @@ void GXDestroyTlutObj(GXTlutObj* obj_) {
|
||||
|
||||
void GXDestroyCopyTex(void* dest) {
|
||||
if (dest != nullptr) {
|
||||
GX_WRITE_AURORA(GX_LOAD_AURORA_DESTROY_COPY_TEX);
|
||||
GX_WRITE_AURORA(GX_AURORA_DESTROY_COPY_TEX);
|
||||
GX_WRITE_U64(reinterpret_cast<u64>(dest));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ void GXSetArray(GXAttr attr, const void* data, u32 size, u8 stride, bool le) {
|
||||
assert((cpIdx & ~0xF) == 0);
|
||||
|
||||
// Write array base
|
||||
GX_WRITE_AURORA(GX_LOAD_AURORA_ARRAYBASE | cpIdx);
|
||||
GX_WRITE_AURORA(GX_AURORA_LOAD_ARRAYBASE | cpIdx);
|
||||
GX_WRITE_U64(reinterpret_cast<u64>(data));
|
||||
GX_WRITE_U32(size);
|
||||
GX_WRITE_U8(le ? 1 : 0);
|
||||
|
||||
@@ -79,7 +79,7 @@ void init_texobj_common(GXTexObj_& obj, const void* data, u16 width, u16 height,
|
||||
}
|
||||
|
||||
void emit_loaded_texobj_metadata(const GXTexObj_& obj, GXTexMapID id) {
|
||||
GX_WRITE_AURORA(GX_LOAD_AURORA_TEXOBJ);
|
||||
GX_WRITE_AURORA(GX_AURORA_LOAD_TEXOBJ);
|
||||
GX_WRITE_U8(static_cast<u8>(id));
|
||||
GX_WRITE_U64(reinterpret_cast<u64>(obj.data));
|
||||
GX_WRITE_U32(obj.width());
|
||||
@@ -92,7 +92,7 @@ void emit_loaded_texobj_metadata(const GXTexObj_& obj, GXTexMapID id) {
|
||||
}
|
||||
|
||||
void emit_loaded_tlut_metadata(const GXTlutObj_& obj, u32 idx) {
|
||||
GX_WRITE_AURORA(GX_LOAD_AURORA_TLUT);
|
||||
GX_WRITE_AURORA(GX_AURORA_LOAD_TLUT);
|
||||
GX_WRITE_U8(static_cast<u8>(idx));
|
||||
GX_WRITE_U64(reinterpret_cast<u64>(obj.data));
|
||||
GX_WRITE_U32(static_cast<u32>(obj.format));
|
||||
|
||||
@@ -29,7 +29,7 @@ void GXBegin(GXPrimitive primitive, GXVtxFmt vtxFmt, u16 nVerts) {
|
||||
sBeginAuto = nVerts == GX_AUTO;
|
||||
if (sBeginAuto) {
|
||||
ASSERT(!aurora::gx::fifo::in_display_list(), "GXBegin: GX_AUTO not supported in display lists");
|
||||
GX_WRITE_AURORA(GX_LOAD_AURORA_DRAW_SIZED);
|
||||
GX_WRITE_AURORA(GX_AURORA_DRAW_SIZED);
|
||||
GX_WRITE_U8(drawCmd);
|
||||
sBeginSizeOffset = aurora::gx::fifo::get_buffer_size();
|
||||
GX_WRITE_U32(0);
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
|
||||
#define GX_WRITE_AURORA(cmd) \
|
||||
do { \
|
||||
GX_WRITE_U8(GX_LOAD_AURORA); \
|
||||
GX_WRITE_U8(GX_AURORA); \
|
||||
GX_WRITE_U16(cmd); \
|
||||
} while (0)
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
#include "gx.hpp"
|
||||
|
||||
#include "../internal.hpp"
|
||||
#include "gx_fmt.hpp"
|
||||
|
||||
static aurora::Module Log("aurora::gx");
|
||||
|
||||
namespace aurora::gx {
|
||||
|
||||
u8 comp_type_size(GXAttr attr, GXCompType type) noexcept {
|
||||
switch (attr) {
|
||||
case GX_VA_PNMTXIDX:
|
||||
case GX_VA_TEX0MTXIDX:
|
||||
case GX_VA_TEX1MTXIDX:
|
||||
case GX_VA_TEX2MTXIDX:
|
||||
case GX_VA_TEX3MTXIDX:
|
||||
case GX_VA_TEX4MTXIDX:
|
||||
case GX_VA_TEX5MTXIDX:
|
||||
case GX_VA_TEX6MTXIDX:
|
||||
case GX_VA_TEX7MTXIDX:
|
||||
return 1;
|
||||
case GX_VA_CLR0:
|
||||
case GX_VA_CLR1:
|
||||
switch (type) {
|
||||
case GX_RGB565:
|
||||
case GX_RGBA4:
|
||||
return 2;
|
||||
case GX_RGB8:
|
||||
case GX_RGBA6:
|
||||
return 3;
|
||||
case GX_RGBX8:
|
||||
case GX_RGBA8:
|
||||
return 4;
|
||||
}
|
||||
default:
|
||||
switch (type) {
|
||||
case GX_U8:
|
||||
case GX_S8:
|
||||
return 1;
|
||||
case GX_U16:
|
||||
case GX_S16:
|
||||
return 2;
|
||||
case GX_F32:
|
||||
return 4;
|
||||
default:
|
||||
Log.fatal("comp_type_size: Unsupported component type {}", type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u8 comp_cnt_count(GXAttr attr, GXCompCnt cnt) noexcept {
|
||||
switch (attr) {
|
||||
case GX_VA_PNMTXIDX:
|
||||
case GX_VA_TEX0MTXIDX:
|
||||
case GX_VA_TEX1MTXIDX:
|
||||
case GX_VA_TEX2MTXIDX:
|
||||
case GX_VA_TEX3MTXIDX:
|
||||
case GX_VA_TEX4MTXIDX:
|
||||
case GX_VA_TEX5MTXIDX:
|
||||
case GX_VA_TEX6MTXIDX:
|
||||
case GX_VA_TEX7MTXIDX:
|
||||
return 1;
|
||||
case GX_VA_POS:
|
||||
switch (cnt) {
|
||||
case GX_POS_XY:
|
||||
return 2;
|
||||
case GX_POS_XYZ:
|
||||
return 3;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GX_VA_NRM:
|
||||
switch (cnt) {
|
||||
case GX_NRM_XYZ:
|
||||
return 3;
|
||||
case GX_NRM_NBT:
|
||||
case GX_NRM_NBT3:
|
||||
return 9;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GX_VA_CLR0:
|
||||
case GX_VA_CLR1:
|
||||
return 1;
|
||||
case GX_VA_TEX0:
|
||||
case GX_VA_TEX1:
|
||||
case GX_VA_TEX2:
|
||||
case GX_VA_TEX3:
|
||||
case GX_VA_TEX4:
|
||||
case GX_VA_TEX5:
|
||||
case GX_VA_TEX6:
|
||||
case GX_VA_TEX7:
|
||||
switch (cnt) {
|
||||
case GX_TEX_S:
|
||||
return 1;
|
||||
case GX_TEX_ST:
|
||||
return 2;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
Log.fatal("comp_cnt_count: Unsupported attr/cnt {} {}", attr, cnt);
|
||||
}
|
||||
|
||||
} // namespace aurora::gx
|
||||
@@ -443,7 +443,7 @@ void process(const u8* data, u32 size, bool bigEndian) {
|
||||
break;
|
||||
}
|
||||
|
||||
case GX_LOAD_AURORA: {
|
||||
case GX_AURORA: {
|
||||
handle_aurora(data, pos, size, bigEndian);
|
||||
break;
|
||||
}
|
||||
@@ -1241,7 +1241,7 @@ static void handle_cp(u8 addr, u32 value, bool bigEndian) {
|
||||
}
|
||||
// Array base addresses (0xA0-0xAF)
|
||||
else if (addr >= 0xA0 && addr <= 0xAF) {
|
||||
Log.error("CP_REG_ARRAYBASE_ID is not supported on Aurora. Use GX_LOAD_AURORA_ARRAYBASE instead.");
|
||||
Log.error("CP_REG_ARRAYBASE_ID is not supported on Aurora. Use GX_AURORA_LOAD_ARRAYBASE instead.");
|
||||
}
|
||||
// Array strides (0xB0-0xBF)
|
||||
else if (addr >= 0xB0 && addr <= 0xBF) {
|
||||
@@ -1538,6 +1538,8 @@ static u32 calculate_last_vtx_size(GXVtxFmt fmt) {
|
||||
}
|
||||
|
||||
static void handle_draw_unmerged(GXPrimitive prim, GXVtxFmt fmt, u16 vtxCount, gfx::Range vertRange);
|
||||
static void push_gx_draw(GXPrimitive prim, GXVtxFmt fmt, u16 vtxCount, gfx::Range vertRange, gfx::Range idxRange,
|
||||
u32 numIndices);
|
||||
|
||||
// Draw command handler - parses vertices inline and caches results
|
||||
static ByteBuffer handle_draw_idx_buf;
|
||||
@@ -1626,6 +1628,11 @@ static void handle_draw_unmerged(GXPrimitive prim, GXVtxFmt fmt, u16 vtxCount, g
|
||||
realBuf.clear();
|
||||
}
|
||||
|
||||
push_gx_draw(prim, fmt, vtxCount, vertRange, idxRange, numIndices);
|
||||
}
|
||||
|
||||
static void push_gx_draw(GXPrimitive prim, GXVtxFmt fmt, u16 vtxCount, gfx::Range vertRange, gfx::Range idxRange,
|
||||
u32 numIndices) {
|
||||
// Build pipeline, bind groups, and push draw command
|
||||
BindGroupRanges ranges{};
|
||||
for (int i = GX_VA_POS; i <= GX_VA_TEX7; ++i) {
|
||||
@@ -1688,8 +1695,8 @@ void handle_aurora(const u8* data, u32& pos, u32 size, bool bigEndian) {
|
||||
pos += 2;
|
||||
|
||||
// Setting of vertex array bases.
|
||||
if (subCmd == GX_LOAD_AURORA_VIEWPORT_RENDER) {
|
||||
CHECK(pos + 24 <= size, "GX_LOAD_AURORA_VIEWPORT_RENDER read overrun");
|
||||
if (subCmd == GX_AURORA_LOAD_VIEWPORT_RENDER) {
|
||||
CHECK(pos + 24 <= size, "GX_AURORA_LOAD_VIEWPORT_RENDER read overrun");
|
||||
const f32 left = read_f32(data + pos, bigEndian);
|
||||
pos += 4;
|
||||
const f32 top = read_f32(data + pos, bigEndian);
|
||||
@@ -1710,8 +1717,8 @@ void handle_aurora(const u8* data, u32& pos, u32 size, bool bigEndian) {
|
||||
.znear = nearZ,
|
||||
.zfar = farZ,
|
||||
});
|
||||
} else if (subCmd == GX_LOAD_AURORA_SCISSOR_RENDER) {
|
||||
CHECK(pos + 16 <= size, "GX_LOAD_AURORA_SCISSOR_RENDER read overrun");
|
||||
} else if (subCmd == GX_AURORA_LOAD_SCISSOR_RENDER) {
|
||||
CHECK(pos + 16 <= size, "GX_AURORA_LOAD_SCISSOR_RENDER read overrun");
|
||||
const int32_t left = static_cast<int32_t>(read_u32(data + pos, bigEndian));
|
||||
pos += 4;
|
||||
const int32_t top = static_cast<int32_t>(read_u32(data + pos, bigEndian));
|
||||
@@ -1721,9 +1728,9 @@ void handle_aurora(const u8* data, u32& pos, u32 size, bool bigEndian) {
|
||||
const int32_t height = static_cast<int32_t>(read_u32(data + pos, bigEndian));
|
||||
pos += 4;
|
||||
set_render_scissor({left, top, width, height});
|
||||
} else if (subCmd >= GX_LOAD_AURORA_ARRAYBASE && subCmd <= (GX_LOAD_AURORA_ARRAYBASE | 0x0f)) {
|
||||
CHECK(pos + 13 <= size, "GX_LOAD_AURORA_ARRAYBASE read overrun");
|
||||
u32 attrIdx = subCmd - GX_LOAD_AURORA_ARRAYBASE + GX_VA_POS;
|
||||
} else if (subCmd >= GX_AURORA_LOAD_ARRAYBASE && subCmd <= (GX_AURORA_LOAD_ARRAYBASE | 0x0f)) {
|
||||
CHECK(pos + 13 <= size, "GX_AURORA_LOAD_ARRAYBASE read overrun");
|
||||
u32 attrIdx = subCmd - GX_AURORA_LOAD_ARRAYBASE + GX_VA_POS;
|
||||
|
||||
u64 arrayAddr = read_u64(data + pos, bigEndian);
|
||||
pos += 8;
|
||||
@@ -1742,8 +1749,8 @@ void handle_aurora(const u8* data, u32& pos, u32 size, bool bigEndian) {
|
||||
array.cachedRange = {};
|
||||
g_gxState.stateDirty = true;
|
||||
}
|
||||
} else if (subCmd == GX_LOAD_AURORA_TEXOBJ) {
|
||||
CHECK(pos + 34 <= size, "GX_LOAD_AURORA_TEXOBJ read overrun");
|
||||
} else if (subCmd == GX_AURORA_LOAD_TEXOBJ) {
|
||||
CHECK(pos + 34 <= size, "GX_AURORA_LOAD_TEXOBJ read overrun");
|
||||
const auto texMapId = data[pos];
|
||||
pos += 1;
|
||||
CHECK(texMapId < MaxTextures, "invalid texture map id {}", texMapId);
|
||||
@@ -1770,8 +1777,8 @@ void handle_aurora(const u8* data, u32& pos, u32 size, bool bigEndian) {
|
||||
pos += 4;
|
||||
slot.set_no_cache(false); // Reset no-cache flag
|
||||
g_gxState.stateDirty = true;
|
||||
} else if (subCmd == GX_LOAD_AURORA_TLUT) {
|
||||
CHECK(pos + 23 <= size, "GX_LOAD_AURORA_TLUT read overrun");
|
||||
} else if (subCmd == GX_AURORA_LOAD_TLUT) {
|
||||
CHECK(pos + 23 <= size, "GX_AURORA_LOAD_TLUT read overrun");
|
||||
const auto idx = data[pos];
|
||||
pos += 1;
|
||||
CHECK(idx < MaxTluts, "invalid tlut slot {}", idx);
|
||||
@@ -1801,20 +1808,20 @@ void handle_aurora(const u8* data, u32& pos, u32 size, bool bigEndian) {
|
||||
g_gxState.clamp = read_f32(data + pos, bigEndian);
|
||||
pos += 4;
|
||||
g_gxState.stateDirty = true;
|
||||
} else if (subCmd == GX_LOAD_AURORA_DESTROY_TEXOBJ) {
|
||||
CHECK(pos + 4 <= size, "GX_LOAD_AURORA_DESTROY_TEXOBJ read overrun");
|
||||
} else if (subCmd == GX_AURORA_DESTROY_TEXOBJ) {
|
||||
CHECK(pos + 4 <= size, "GX_AURORA_DESTROY_TEXOBJ read overrun");
|
||||
evict_texture_object(read_u32(data + pos, bigEndian));
|
||||
pos += 4;
|
||||
} else if (subCmd == GX_LOAD_AURORA_DESTROY_TLUT) {
|
||||
CHECK(pos + 4 <= size, "GX_LOAD_AURORA_DESTROY_TLUT read overrun");
|
||||
} else if (subCmd == GX_AURORA_DESTROY_TLUT) {
|
||||
CHECK(pos + 4 <= size, "GX_AURORA_DESTROY_TLUT read overrun");
|
||||
evict_tlut_object(read_u32(data + pos, bigEndian));
|
||||
pos += 4;
|
||||
} else if (subCmd == GX_LOAD_AURORA_DESTROY_COPY_TEX) {
|
||||
CHECK(pos + 8 <= size, "GX_LOAD_AURORA_DESTROY_COPY_TEX read overrun");
|
||||
} else if (subCmd == GX_AURORA_DESTROY_COPY_TEX) {
|
||||
CHECK(pos + 8 <= size, "GX_AURORA_DESTROY_COPY_TEX read overrun");
|
||||
evict_copy_texture(reinterpret_cast<const void*>(read_u64(data + pos, bigEndian)));
|
||||
pos += 8;
|
||||
} else if (subCmd == GX_LOAD_AURORA_DRAW_SIZED) {
|
||||
CHECK(pos + 5 <= size, "GX_LOAD_AURORA_DRAW_SIZED read overrun");
|
||||
} else if (subCmd == GX_AURORA_DRAW_SIZED) {
|
||||
CHECK(pos + 5 <= size, "GX_AURORA_DRAW_SIZED read overrun");
|
||||
u8 cmd = data[pos];
|
||||
pos += 1;
|
||||
u32 byteLen = read_u32(data + pos, bigEndian);
|
||||
@@ -1829,17 +1836,48 @@ void handle_aurora(const u8* data, u32& pos, u32 size, bool bigEndian) {
|
||||
vtxSize = calculate_last_vtx_size(fmt);
|
||||
}
|
||||
ASSERT(vtxSize != 0 && byteLen % vtxSize == 0,
|
||||
"GX_LOAD_AURORA_DRAW_SIZED: {} bytes is not a whole number of size-{} vertices", byteLen, vtxSize);
|
||||
"GX_AURORA_DRAW_SIZED: {} bytes is not a whole number of size-{} vertices", byteLen, vtxSize);
|
||||
u32 vtxCount = byteLen / vtxSize;
|
||||
ASSERT(vtxCount <= 0xFFFF, "GX_LOAD_AURORA_DRAW_SIZED: too many vertices ({})", vtxCount);
|
||||
ASSERT(vtxCount <= 0xFFFF, "GX_AURORA_DRAW_SIZED: too many vertices ({})", vtxCount);
|
||||
draw_prim(prim, fmt, static_cast<u16>(vtxCount), data, pos, size);
|
||||
}
|
||||
} else if (subCmd == GX_LOAD_AURORA_DEBUG_GROUP_PUSH) {
|
||||
} else if (subCmd == GX_AURORA_DRAW_INDEXED) {
|
||||
ZoneScopedN("DRAW_INDEXED");
|
||||
CHECK(pos + 7 <= size, "GX_AURORA_DRAW_INDEXED read overrun");
|
||||
const u8 cmd = data[pos];
|
||||
pos += 1;
|
||||
const u16 vtxCount = read_u16(data + pos, bigEndian);
|
||||
pos += 2;
|
||||
const u32 indexCount = read_u32(data + pos, bigEndian);
|
||||
pos += 4;
|
||||
const GXVtxFmt fmt = static_cast<GXVtxFmt>(cmd & CP_VAT_MASK);
|
||||
const GXPrimitive prim = static_cast<GXPrimitive>(cmd & CP_OPCODE_MASK);
|
||||
ASSERT(prim == GX_TRIANGLES, "GX_AURORA_DRAW_INDEXED: primitive must be GX_TRIANGLES, got {}",
|
||||
static_cast<u32>(prim));
|
||||
const u32 idxBytes = indexCount * static_cast<u32>(sizeof(u16));
|
||||
CHECK(pos + idxBytes <= size, "GX_AURORA_DRAW_INDEXED index data overrun");
|
||||
// Index data is always host-endian; push it to the GPU buffer as-is
|
||||
const gfx::Range idxRange = gfx::push_indices(data + pos, idxBytes, 4);
|
||||
pos += idxBytes;
|
||||
u32 vtxSize;
|
||||
if (g_gxState.lastVtxFmt == fmt) {
|
||||
vtxSize = g_gxState.lastVtxSize;
|
||||
} else {
|
||||
vtxSize = calculate_last_vtx_size(fmt);
|
||||
}
|
||||
const u32 totalVtxBytes = vtxCount * vtxSize;
|
||||
CHECK(pos + totalVtxBytes <= size, "GX_AURORA_DRAW_INDEXED vertex data overrun");
|
||||
const gfx::Range vertRange = gfx::push_verts(data + pos, totalVtxBytes, 4);
|
||||
pos += totalVtxBytes;
|
||||
if (indexCount != 0) {
|
||||
push_gx_draw(prim, fmt, vtxCount, vertRange, idxRange, indexCount);
|
||||
}
|
||||
} else if (subCmd == GX_AURORA_DEBUG_GROUP_PUSH) {
|
||||
auto label = read_string(data, pos, size, bigEndian);
|
||||
gfx::push_debug_group(std::move(label));
|
||||
} else if (subCmd == GX_LOAD_AURORA_DEBUG_GROUP_POP) {
|
||||
} else if (subCmd == GX_AURORA_DEBUG_GROUP_POP) {
|
||||
pop_debug_group();
|
||||
} else if (subCmd == GX_LOAD_AURORA_DEBUG_MARKER_INSERT) {
|
||||
} else if (subCmd == GX_AURORA_DEBUG_MARKER_INSERT) {
|
||||
auto label = read_string(data, pos, size, bigEndian);
|
||||
gfx::insert_debug_marker(std::move(label));
|
||||
}
|
||||
|
||||
+357
@@ -0,0 +1,357 @@
|
||||
#include "aurora/dl.hpp"
|
||||
|
||||
#include <dolphin/gx/GXAurora.h>
|
||||
#include <dolphin/gx/GXCommandList.h>
|
||||
|
||||
#include "../internal.hpp"
|
||||
#include "gx.hpp"
|
||||
|
||||
namespace aurora::gx::dl {
|
||||
static Module Log("aurora::gx::dl");
|
||||
|
||||
static bool is_draw_opcode(u8 opcode) {
|
||||
return opcode == GX_QUADS || opcode == GX_TRIANGLES || opcode == GX_TRIANGLESTRIP || opcode == GX_TRIANGLEFAN ||
|
||||
opcode == GX_LINES || opcode == GX_LINESTRIP || opcode == GX_POINTS;
|
||||
}
|
||||
|
||||
static u16 read_be16(const u8* data) { return static_cast<u16>(data[0]) << 8 | data[1]; }
|
||||
|
||||
static u32 read_be32(const u8* data) {
|
||||
return static_cast<u32>(data[0]) << 24 | static_cast<u32>(data[1]) << 16 | static_cast<u32>(data[2]) << 8 | data[3];
|
||||
}
|
||||
|
||||
static const GXVtxAttrFmtList* find_attr_fmt(const GXVtxAttrFmtList* list, GXAttr attr) {
|
||||
if (list == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
for (; list->attr != GX_VA_NULL; ++list) {
|
||||
if (list->attr == attr) {
|
||||
return list;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::optional<VtxLayout> compute_layout(const GXVtxDescList* desc, const GXVtxAttrFmtList* fmt) {
|
||||
VtxLayout out{};
|
||||
u32 stride = 0;
|
||||
for (; desc->attr != GX_VA_NULL; ++desc) {
|
||||
if (desc->attr >= GX_VA_MAX_ATTR) {
|
||||
return std::nullopt;
|
||||
}
|
||||
u32 size = 0;
|
||||
switch (desc->type) {
|
||||
case GX_NONE:
|
||||
continue;
|
||||
case GX_DIRECT: {
|
||||
if (desc->attr >= GX_VA_PNMTXIDX && desc->attr <= GX_VA_TEX7MTXIDX) {
|
||||
size = 1;
|
||||
break;
|
||||
}
|
||||
const auto* attrFmt = find_attr_fmt(fmt, desc->attr);
|
||||
if (attrFmt == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
size = comp_type_size(desc->attr, attrFmt->type) * comp_cnt_count(desc->attr, attrFmt->cnt);
|
||||
break;
|
||||
}
|
||||
case GX_INDEX8:
|
||||
size = 1;
|
||||
break;
|
||||
case GX_INDEX16:
|
||||
size = 2;
|
||||
break;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
if (desc->attr == GX_VA_NRM && (desc->type == GX_INDEX8 || desc->type == GX_INDEX16)) {
|
||||
// GX_NRM_NBT3 normals are three separate indices
|
||||
const auto* attrFmt = find_attr_fmt(fmt, GX_VA_NRM);
|
||||
if (attrFmt != nullptr && attrFmt->cnt == GX_NRM_NBT3) {
|
||||
size *= 3;
|
||||
}
|
||||
}
|
||||
auto& attr = out.attrs[desc->attr];
|
||||
attr.offset = static_cast<u8>(stride);
|
||||
attr.size = static_cast<u8>(size);
|
||||
attr.type = desc->type;
|
||||
stride += size;
|
||||
if (stride > 0xFF) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
if (stride == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
out.stride = static_cast<u8>(stride);
|
||||
return out;
|
||||
}
|
||||
|
||||
u16 DrawCmd::attr_idx(u32 vtxIdx, GXAttr attr) const {
|
||||
const auto& a = layout->attrs[attr];
|
||||
const u8* ptr = vertices + vtxIdx * layout->stride + a.offset;
|
||||
switch (a.type) {
|
||||
case GX_INDEX8:
|
||||
return ptr[0];
|
||||
case GX_INDEX16:
|
||||
return read_be16(ptr);
|
||||
case GX_DIRECT: // *MTXIDX only
|
||||
return ptr[0];
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
u16 DrawCmd::index(u32 i) const {
|
||||
u16 value;
|
||||
std::memcpy(&value, indices + i * sizeof(u16), sizeof(u16));
|
||||
return value;
|
||||
}
|
||||
|
||||
Reader::Reader(const u8* dl, u32 size, const GXVtxDescList* desc, const VtxFmtLists* fmts)
|
||||
: mData(dl), mSize(size), mDesc(desc), mFmts(fmts) {}
|
||||
|
||||
Reader::Reader(const u8* dl, u32 size, u8 stride) : mData(dl), mSize(size) {
|
||||
VtxLayout layout{};
|
||||
layout.stride = stride;
|
||||
for (u32 fmt = 0; fmt < GX_MAX_VTXFMT; ++fmt) {
|
||||
mLayouts[fmt] = layout;
|
||||
mLayoutComputed[fmt] = true;
|
||||
}
|
||||
}
|
||||
|
||||
const VtxLayout* Reader::layout(GXVtxFmt fmt) {
|
||||
if (!mLayoutComputed[fmt]) {
|
||||
mLayoutComputed[fmt] = true;
|
||||
if (mDesc != nullptr) {
|
||||
mLayouts[fmt] = compute_layout(mDesc, mFmts != nullptr ? (*mFmts)[fmt] : nullptr);
|
||||
}
|
||||
}
|
||||
return mLayouts[fmt].has_value() ? &*mLayouts[fmt] : nullptr;
|
||||
}
|
||||
|
||||
std::optional<Command> Reader::next() {
|
||||
if (mFailed || mPos >= mSize) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const u32 start = mPos;
|
||||
const u8 cmd = mData[start];
|
||||
const u8 opcode = cmd & GX_OPCODE_MASK;
|
||||
|
||||
const auto fail = [&](const char* what) -> std::optional<Command> {
|
||||
Log.warn("Reader: {} (opcode 0x{:02X} at offset {})", what, cmd, start);
|
||||
mFailed = true;
|
||||
return std::nullopt;
|
||||
};
|
||||
const auto passthrough = [&](const u32 cmdSize) -> std::optional<Command> {
|
||||
if (start + cmdSize > mSize) {
|
||||
return fail("command overrun");
|
||||
}
|
||||
mPos = start + cmdSize;
|
||||
return Command{Command::Kind::Passthrough, mData + start, cmdSize, {}};
|
||||
};
|
||||
|
||||
switch (opcode) {
|
||||
case GX_NOP:
|
||||
case GX_CMD_INVL_VC:
|
||||
return passthrough(1);
|
||||
case GX_LOAD_BP_REG & GX_OPCODE_MASK:
|
||||
return passthrough(5);
|
||||
case GX_LOAD_CP_REG:
|
||||
return passthrough(6);
|
||||
case GX_LOAD_XF_REG: {
|
||||
if (start + 5 > mSize) {
|
||||
return fail("XF load overrun");
|
||||
}
|
||||
const u32 count = read_be16(mData + start + 1) + 1;
|
||||
return passthrough(5 + count * 4);
|
||||
}
|
||||
case GX_LOAD_INDX_A:
|
||||
case GX_LOAD_INDX_B:
|
||||
case GX_LOAD_INDX_C:
|
||||
case GX_LOAD_INDX_D:
|
||||
return passthrough(5);
|
||||
case GX_CMD_CALL_DL:
|
||||
return passthrough(9);
|
||||
case GX_AURORA: {
|
||||
if (start + 3 > mSize) {
|
||||
return fail("Aurora subcommand overrun");
|
||||
}
|
||||
if (read_be16(mData + start + 1) != GX_AURORA_DRAW_INDEXED) {
|
||||
return fail("unsupported Aurora subcommand");
|
||||
}
|
||||
if (start + 10 > mSize) {
|
||||
return fail("DRAW_INDEXED header overrun");
|
||||
}
|
||||
const u8 drawCmd = mData[start + 3];
|
||||
const auto fmt = static_cast<GXVtxFmt>(drawCmd & GX_VAT_MASK);
|
||||
const u16 vtxCount = read_be16(mData + start + 4);
|
||||
const u32 indexCount = read_be32(mData + start + 6);
|
||||
const VtxLayout* lo = layout(fmt);
|
||||
if (lo == nullptr) {
|
||||
return fail("no layout for DRAW_INDEXED vertex format");
|
||||
}
|
||||
const u32 idxBytes = indexCount * sizeof(u16);
|
||||
const u32 cmdSize = 10 + idxBytes + vtxCount * lo->stride;
|
||||
if (start + cmdSize > mSize) {
|
||||
return fail("DRAW_INDEXED data overrun");
|
||||
}
|
||||
mPos = start + cmdSize;
|
||||
return Command{
|
||||
Command::Kind::DrawIndexed,
|
||||
mData + start,
|
||||
cmdSize,
|
||||
DrawCmd{
|
||||
static_cast<GXPrimitive>(drawCmd & GX_OPCODE_MASK),
|
||||
fmt,
|
||||
vtxCount,
|
||||
mData + start + 10 + idxBytes,
|
||||
lo,
|
||||
mData + start + 10,
|
||||
indexCount,
|
||||
},
|
||||
};
|
||||
}
|
||||
default: {
|
||||
if (!is_draw_opcode(opcode)) {
|
||||
return fail("unknown opcode");
|
||||
}
|
||||
if (start + 3 > mSize) {
|
||||
return fail("draw header overrun");
|
||||
}
|
||||
const auto fmt = static_cast<GXVtxFmt>(cmd & GX_VAT_MASK);
|
||||
const u16 vtxCount = read_be16(mData + start + 1);
|
||||
const VtxLayout* lo = layout(fmt);
|
||||
if (lo == nullptr) {
|
||||
return fail("no layout for draw vertex format");
|
||||
}
|
||||
const u32 cmdSize = 3 + vtxCount * lo->stride;
|
||||
if (start + cmdSize > mSize) {
|
||||
return fail("draw data overrun");
|
||||
}
|
||||
mPos = start + cmdSize;
|
||||
return Command{
|
||||
Command::Kind::Draw,
|
||||
mData + start,
|
||||
cmdSize,
|
||||
DrawCmd{
|
||||
static_cast<GXPrimitive>(opcode),
|
||||
fmt,
|
||||
vtxCount,
|
||||
mData + start + 3,
|
||||
lo,
|
||||
nullptr,
|
||||
0,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct DrawBatch {
|
||||
GXVtxFmt fmt = GX_VTXFMT0;
|
||||
u16 vtxCount = 0;
|
||||
bool allTriangles = true;
|
||||
std::vector<u8> verts;
|
||||
std::vector<u16> indices;
|
||||
};
|
||||
|
||||
void push_be16(std::vector<u8>& out, u16 value) {
|
||||
out.push_back(value >> 8);
|
||||
out.push_back(value & 0xFF);
|
||||
}
|
||||
|
||||
void push_be32(std::vector<u8>& out, u32 value) {
|
||||
out.push_back(value >> 24);
|
||||
out.push_back(value >> 16 & 0xFF);
|
||||
out.push_back(value >> 8 & 0xFF);
|
||||
out.push_back(value & 0xFF);
|
||||
}
|
||||
|
||||
void flush_batch(std::vector<u8>& out, DrawBatch& batch) {
|
||||
if (batch.vtxCount != 0) {
|
||||
if (batch.allTriangles) {
|
||||
// plain triangle draw does not require an index buffer
|
||||
out.push_back(static_cast<u8>(GX_TRIANGLES) | static_cast<u8>(batch.fmt));
|
||||
push_be16(out, batch.vtxCount);
|
||||
} else {
|
||||
out.push_back(GX_AURORA);
|
||||
push_be16(out, GX_AURORA_DRAW_INDEXED);
|
||||
out.push_back(static_cast<u8>(GX_TRIANGLES) | static_cast<u8>(batch.fmt));
|
||||
push_be16(out, batch.vtxCount);
|
||||
push_be32(out, static_cast<u32>(batch.indices.size()));
|
||||
// index data is host-endian; see GX_AURORA_DRAW_INDEXED
|
||||
const auto* idxData = reinterpret_cast<const u8*>(batch.indices.data());
|
||||
out.insert(out.end(), idxData, idxData + batch.indices.size() * sizeof(u16));
|
||||
}
|
||||
out.insert(out.end(), batch.verts.begin(), batch.verts.end());
|
||||
}
|
||||
batch.vtxCount = 0;
|
||||
batch.allTriangles = true;
|
||||
batch.verts.clear();
|
||||
batch.indices.clear();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<std::vector<u8>> optimize(const u8* dl, u32 size, const GXVtxDescList* desc, const VtxFmtLists* fmts) {
|
||||
Reader reader{dl, size, desc, fmts};
|
||||
std::vector<u8> out;
|
||||
out.reserve(size);
|
||||
DrawBatch batch;
|
||||
|
||||
while (const auto cmd = reader.next()) {
|
||||
const auto copy_verbatim = [&] {
|
||||
flush_batch(out, batch);
|
||||
out.insert(out.end(), cmd->data, cmd->data + cmd->size);
|
||||
};
|
||||
|
||||
switch (cmd->kind) {
|
||||
case Command::Kind::Passthrough:
|
||||
if (cmd->data[0] != GX_NOP) {
|
||||
copy_verbatim();
|
||||
}
|
||||
break;
|
||||
case Command::Kind::DrawIndexed:
|
||||
copy_verbatim();
|
||||
break;
|
||||
case Command::Kind::Draw: {
|
||||
const auto& draw = cmd->draw;
|
||||
if (batch.vtxCount != 0 && (batch.fmt != draw.fmt || static_cast<u32>(batch.vtxCount) + draw.vtxCount > 0xFFFF)) {
|
||||
flush_batch(out, batch);
|
||||
}
|
||||
const u16 base = batch.vtxCount;
|
||||
const bool expanded = expand_triangles(draw.prim, draw.vtxCount, [&](u16 i0, u16 i1, u16 i2) {
|
||||
batch.indices.push_back(base + i0);
|
||||
batch.indices.push_back(base + i1);
|
||||
batch.indices.push_back(base + i2);
|
||||
});
|
||||
if (!expanded) {
|
||||
// lines/points or degenerate counts; expand_triangles validates before emitting
|
||||
copy_verbatim();
|
||||
break;
|
||||
}
|
||||
if (batch.vtxCount == 0) {
|
||||
batch.fmt = draw.fmt;
|
||||
}
|
||||
if (draw.prim != GX_TRIANGLES) {
|
||||
batch.allTriangles = false;
|
||||
}
|
||||
batch.verts.insert(batch.verts.end(), draw.vertices, draw.vertices + draw.vtxCount * draw.layout->stride);
|
||||
batch.vtxCount += draw.vtxCount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reader.failed()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
flush_batch(out, batch);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace aurora::gx::dl
|
||||
-100
@@ -677,106 +677,6 @@ wgpu::RenderPipeline build_pipeline(const PipelineConfig& config, ArrayRef<wgpu:
|
||||
return g_device.CreateRenderPipeline(&descriptor);
|
||||
}
|
||||
|
||||
u8 comp_type_size(GXAttr attr, GXCompType type) noexcept {
|
||||
switch (attr) {
|
||||
case GX_VA_PNMTXIDX:
|
||||
case GX_VA_TEX0MTXIDX:
|
||||
case GX_VA_TEX1MTXIDX:
|
||||
case GX_VA_TEX2MTXIDX:
|
||||
case GX_VA_TEX3MTXIDX:
|
||||
case GX_VA_TEX4MTXIDX:
|
||||
case GX_VA_TEX5MTXIDX:
|
||||
case GX_VA_TEX6MTXIDX:
|
||||
case GX_VA_TEX7MTXIDX:
|
||||
return 1;
|
||||
case GX_VA_CLR0:
|
||||
case GX_VA_CLR1:
|
||||
switch (type) {
|
||||
case GX_RGB565:
|
||||
case GX_RGBA4:
|
||||
return 2;
|
||||
case GX_RGB8:
|
||||
case GX_RGBA6:
|
||||
return 3;
|
||||
case GX_RGBX8:
|
||||
case GX_RGBA8:
|
||||
return 4;
|
||||
}
|
||||
default:
|
||||
switch (type) {
|
||||
case GX_U8:
|
||||
case GX_S8:
|
||||
return 1;
|
||||
case GX_U16:
|
||||
case GX_S16:
|
||||
return 2;
|
||||
case GX_F32:
|
||||
return 4;
|
||||
default:
|
||||
Log.fatal("comp_type_size: Unsupported component type {}", type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u8 comp_cnt_count(GXAttr attr, GXCompCnt cnt) noexcept {
|
||||
switch (attr) {
|
||||
case GX_VA_PNMTXIDX:
|
||||
case GX_VA_TEX0MTXIDX:
|
||||
case GX_VA_TEX1MTXIDX:
|
||||
case GX_VA_TEX2MTXIDX:
|
||||
case GX_VA_TEX3MTXIDX:
|
||||
case GX_VA_TEX4MTXIDX:
|
||||
case GX_VA_TEX5MTXIDX:
|
||||
case GX_VA_TEX6MTXIDX:
|
||||
case GX_VA_TEX7MTXIDX:
|
||||
return 1;
|
||||
case GX_VA_POS:
|
||||
switch (cnt) {
|
||||
case GX_POS_XY:
|
||||
return 2;
|
||||
case GX_POS_XYZ:
|
||||
return 3;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GX_VA_NRM:
|
||||
switch (cnt) {
|
||||
case GX_NRM_XYZ:
|
||||
return 3;
|
||||
case GX_NRM_NBT:
|
||||
case GX_NRM_NBT3:
|
||||
return 9;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GX_VA_CLR0:
|
||||
case GX_VA_CLR1:
|
||||
return 1;
|
||||
case GX_VA_TEX0:
|
||||
case GX_VA_TEX1:
|
||||
case GX_VA_TEX2:
|
||||
case GX_VA_TEX3:
|
||||
case GX_VA_TEX4:
|
||||
case GX_VA_TEX5:
|
||||
case GX_VA_TEX6:
|
||||
case GX_VA_TEX7:
|
||||
switch (cnt) {
|
||||
case GX_TEX_S:
|
||||
return 1;
|
||||
case GX_TEX_ST:
|
||||
return 2;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
Log.fatal("comp_cnt_count: Unsupported attr/cnt {} {}", attr, cnt);
|
||||
}
|
||||
|
||||
void populate_pipeline_config(PipelineConfig& config, GXPrimitive primitive, GXVtxFmt fmt) noexcept {
|
||||
ZoneScoped;
|
||||
|
||||
|
||||
@@ -40,6 +40,10 @@ if (AURORA_ENABLE_GX)
|
||||
# FIFO/command processor (encoder + decoder)
|
||||
../lib/gx/fifo.cpp
|
||||
../lib/gx/command_processor.cpp
|
||||
# Display list reader/optimizer
|
||||
../lib/gx/attr_fmt.cpp
|
||||
../lib/gx/dl.cpp
|
||||
gx_dl_test.cpp
|
||||
)
|
||||
|
||||
target_include_directories(gx_fifo_tests PRIVATE
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
#include "gx_test_common.hpp"
|
||||
|
||||
#include "aurora/dl.hpp"
|
||||
#include "dolphin/gx/GXAurora.h"
|
||||
#include "gx/pipeline.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
using namespace aurora::gx::dl;
|
||||
|
||||
namespace aurora::gfx {
|
||||
extern gx::DrawData g_testLastDraw;
|
||||
extern uint32_t g_testDrawCount;
|
||||
} // namespace aurora::gfx
|
||||
|
||||
namespace {
|
||||
|
||||
const GXVtxDescList kPosClrDesc[] = {
|
||||
{GX_VA_POS, GX_INDEX8},
|
||||
{GX_VA_CLR0, GX_INDEX8},
|
||||
{GX_VA_NULL, GX_NONE},
|
||||
};
|
||||
|
||||
const GXVtxDescList kVtxDesc[] = {
|
||||
{GX_VA_POS, GX_INDEX8}, {GX_VA_NRM, GX_INDEX8}, {GX_VA_CLR0, GX_INDEX8},
|
||||
{GX_VA_TEX0, GX_INDEX8}, {GX_VA_NULL, GX_NONE},
|
||||
};
|
||||
|
||||
u8 op(GXPrimitive prim, GXVtxFmt fmt) { return static_cast<u8>(prim) | static_cast<u8>(fmt); }
|
||||
|
||||
void be16(std::vector<u8>& out, u16 value) {
|
||||
out.push_back(value >> 8);
|
||||
out.push_back(value & 0xFF);
|
||||
}
|
||||
|
||||
void draw_cmd(std::vector<u8>& out, u8 opcode, u16 vtxCount, std::initializer_list<u8> vertices) {
|
||||
out.push_back(opcode);
|
||||
be16(out, vtxCount);
|
||||
out.insert(out.end(), vertices);
|
||||
}
|
||||
|
||||
u16 host_u16(const u8* data) {
|
||||
u16 value;
|
||||
std::memcpy(&value, data, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
std::vector<std::array<u16, 3>> collect_triangles(GXPrimitive prim, u16 vtxCount) {
|
||||
std::vector<std::array<u16, 3>> tris;
|
||||
expand_triangles(prim, vtxCount, [&](u16 i0, u16 i1, u16 i2) { tris.push_back({i0, i1, i2}); });
|
||||
return tris;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(GXDlReader, WalksLeafStripDl) {
|
||||
std::vector<u8> dl;
|
||||
dl.push_back(GX_NOP);
|
||||
// 4-vertex strip, vertices are (pos, nrm, clr, tex) index tuples
|
||||
draw_cmd(dl, op(GX_TRIANGLESTRIP, GX_VTXFMT0), 4, {0, 0, 0, 0, 1, 0, 1, 1, 2, 0, 2, 2, 3, 0, 3, 3});
|
||||
dl.push_back(GX_NOP);
|
||||
|
||||
Reader reader{dl.data(), static_cast<u32>(dl.size()), kVtxDesc};
|
||||
|
||||
auto cmd = reader.next();
|
||||
ASSERT_TRUE(cmd.has_value());
|
||||
EXPECT_EQ(cmd->kind, Command::Kind::Passthrough);
|
||||
EXPECT_EQ(cmd->size, 1u);
|
||||
|
||||
cmd = reader.next();
|
||||
ASSERT_TRUE(cmd.has_value());
|
||||
ASSERT_EQ(cmd->kind, Command::Kind::Draw);
|
||||
EXPECT_EQ(cmd->draw.prim, GX_TRIANGLESTRIP);
|
||||
EXPECT_EQ(cmd->draw.fmt, GX_VTXFMT0);
|
||||
EXPECT_EQ(cmd->draw.vtxCount, 4);
|
||||
EXPECT_EQ(cmd->draw.layout->stride, 4);
|
||||
EXPECT_EQ(cmd->draw.attr_idx(0, GX_VA_POS), 0);
|
||||
EXPECT_EQ(cmd->draw.attr_idx(2, GX_VA_POS), 2);
|
||||
EXPECT_EQ(cmd->draw.attr_idx(2, GX_VA_CLR0), 2);
|
||||
EXPECT_EQ(cmd->draw.attr_idx(3, GX_VA_TEX0), 3);
|
||||
EXPECT_EQ(cmd->draw.attr_idx(3, GX_VA_NRM), 0);
|
||||
|
||||
cmd = reader.next();
|
||||
ASSERT_TRUE(cmd.has_value());
|
||||
EXPECT_EQ(cmd->kind, Command::Kind::Passthrough);
|
||||
|
||||
EXPECT_FALSE(reader.next().has_value());
|
||||
EXPECT_FALSE(reader.failed());
|
||||
}
|
||||
|
||||
TEST(GXDlReader, FailsOnUnknownOpcode) {
|
||||
const std::vector<u8> dl{0x70, 0x00, 0x00};
|
||||
Reader reader{dl.data(), static_cast<u32>(dl.size()), kPosClrDesc};
|
||||
EXPECT_FALSE(reader.next().has_value());
|
||||
EXPECT_TRUE(reader.failed());
|
||||
}
|
||||
|
||||
TEST(GXDlReader, FailsOnDrawOverrun) {
|
||||
std::vector<u8> dl;
|
||||
draw_cmd(dl, op(GX_TRIANGLESTRIP, GX_VTXFMT0), 100, {0, 0, 1, 1});
|
||||
Reader reader{dl.data(), static_cast<u32>(dl.size()), kPosClrDesc};
|
||||
EXPECT_FALSE(reader.next().has_value());
|
||||
EXPECT_TRUE(reader.failed());
|
||||
}
|
||||
|
||||
TEST(GXDlReader, StrideOnlyWalksAndSizes) {
|
||||
std::vector<u8> dl;
|
||||
draw_cmd(dl, op(GX_TRIANGLESTRIP, GX_VTXFMT0), 3, {0, 0, 1, 1, 2, 2});
|
||||
// BP write passes through
|
||||
dl.insert(dl.end(), {0x61, 0x41, 0x00, 0x00, 0x01});
|
||||
draw_cmd(dl, op(GX_TRIANGLES, GX_VTXFMT0), 3, {0, 0, 1, 1, 2, 2});
|
||||
|
||||
Reader reader{dl.data(), static_cast<u32>(dl.size()), static_cast<u8>(2)};
|
||||
u32 vtxTotal = 0;
|
||||
u32 passthrough = 0;
|
||||
while (const auto cmd = reader.next()) {
|
||||
if (cmd->kind == Command::Kind::Draw) {
|
||||
vtxTotal += cmd->draw.vtxCount;
|
||||
} else {
|
||||
++passthrough;
|
||||
}
|
||||
}
|
||||
EXPECT_FALSE(reader.failed());
|
||||
EXPECT_EQ(vtxTotal, 6u);
|
||||
EXPECT_EQ(passthrough, 1u);
|
||||
}
|
||||
|
||||
TEST(GXDlExpand, StripFanQuadWinding) {
|
||||
using Tri = std::array<u16, 3>;
|
||||
EXPECT_EQ(collect_triangles(GX_TRIANGLESTRIP, 5), (std::vector<Tri>{{0, 1, 2}, {2, 1, 3}, {2, 3, 4}}));
|
||||
EXPECT_EQ(collect_triangles(GX_TRIANGLEFAN, 4), (std::vector<Tri>{{0, 1, 2}, {0, 2, 3}}));
|
||||
EXPECT_EQ(collect_triangles(GX_QUADS, 8), (std::vector<Tri>{{0, 1, 2}, {2, 3, 0}, {4, 5, 6}, {6, 7, 4}}));
|
||||
EXPECT_EQ(collect_triangles(GX_TRIANGLES, 3), (std::vector<Tri>{{0, 1, 2}}));
|
||||
|
||||
EXPECT_FALSE(expand_triangles(GX_LINES, 4, [](u16, u16, u16) {}));
|
||||
EXPECT_FALSE(expand_triangles(GX_TRIANGLESTRIP, 2, [](u16, u16, u16) {}));
|
||||
EXPECT_FALSE(expand_triangles(GX_QUADS, 6, [](u16, u16, u16) {}));
|
||||
}
|
||||
|
||||
TEST(GXDlOptimize, MergesAdjacentStrips) {
|
||||
std::vector<u8> dl;
|
||||
draw_cmd(dl, op(GX_TRIANGLESTRIP, GX_VTXFMT0), 4, {0, 0, 1, 1, 2, 2, 3, 3});
|
||||
draw_cmd(dl, op(GX_TRIANGLESTRIP, GX_VTXFMT0), 4, {4, 4, 5, 5, 6, 6, 7, 7});
|
||||
dl.push_back(GX_NOP);
|
||||
|
||||
const auto result = optimize(dl.data(), static_cast<u32>(dl.size()), kPosClrDesc);
|
||||
ASSERT_TRUE(result.has_value());
|
||||
const auto& out = *result;
|
||||
|
||||
// One DRAW_INDEXED command: 10-byte header, 12 u16 indices, 8 2-byte vertices
|
||||
ASSERT_EQ(out.size(), 10u + 12 * 2 + 8 * 2);
|
||||
EXPECT_EQ(out[0], GX_AURORA);
|
||||
EXPECT_EQ((out[1] << 8 | out[2]), GX_AURORA_DRAW_INDEXED);
|
||||
EXPECT_EQ(out[3], op(GX_TRIANGLES, GX_VTXFMT0));
|
||||
EXPECT_EQ((out[4] << 8 | out[5]), 8); // vtxCount
|
||||
EXPECT_EQ((out[6] << 24 | out[7] << 16 | out[8] << 8 | out[9]), 12); // indexCount
|
||||
|
||||
// Host-endian indices: strip 0 at base 0, strip 1 at base 4
|
||||
const u16 expected[12] = {0, 1, 2, 2, 1, 3, 4, 5, 6, 6, 5, 7};
|
||||
for (int i = 0; i < 12; i++) {
|
||||
EXPECT_EQ(host_u16(out.data() + 10 + i * 2), expected[i]) << "index " << i;
|
||||
}
|
||||
|
||||
// Vertex tuples concatenated verbatim
|
||||
const u8 expectedVerts[16] = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7};
|
||||
EXPECT_EQ(std::memcmp(out.data() + 10 + 12 * 2, expectedVerts, sizeof(expectedVerts)), 0);
|
||||
}
|
||||
|
||||
TEST(GXDlOptimize, PureTrianglesStayPlain) {
|
||||
std::vector<u8> dl;
|
||||
draw_cmd(dl, op(GX_TRIANGLES, GX_VTXFMT0), 3, {0, 0, 1, 1, 2, 2});
|
||||
draw_cmd(dl, op(GX_TRIANGLES, GX_VTXFMT0), 3, {3, 3, 4, 4, 5, 5});
|
||||
|
||||
const auto result = optimize(dl.data(), static_cast<u32>(dl.size()), kPosClrDesc);
|
||||
ASSERT_TRUE(result.has_value());
|
||||
const auto& out = *result;
|
||||
|
||||
// Merged into a single plain triangles draw (no index buffer needed at runtime)
|
||||
ASSERT_EQ(out.size(), 3u + 6 * 2);
|
||||
EXPECT_EQ(out[0], op(GX_TRIANGLES, GX_VTXFMT0));
|
||||
EXPECT_EQ((out[1] << 8 | out[2]), 6);
|
||||
}
|
||||
|
||||
TEST(GXDlOptimize, StateCommandIsBarrier) {
|
||||
std::vector<u8> dl;
|
||||
draw_cmd(dl, op(GX_TRIANGLESTRIP, GX_VTXFMT0), 3, {0, 0, 1, 1, 2, 2});
|
||||
const u8 bpCmd[] = {0x61, 0x41, 0x00, 0x00, 0x01};
|
||||
dl.insert(dl.end(), std::begin(bpCmd), std::end(bpCmd));
|
||||
draw_cmd(dl, op(GX_TRIANGLESTRIP, GX_VTXFMT0), 3, {3, 3, 4, 4, 5, 5});
|
||||
|
||||
const auto result = optimize(dl.data(), static_cast<u32>(dl.size()), kPosClrDesc);
|
||||
ASSERT_TRUE(result.has_value());
|
||||
const auto& out = *result;
|
||||
|
||||
// DRAW_INDEXED(3 verts, 3 indices), BP, DRAW_INDEXED(3 verts, 3 indices)
|
||||
const u32 drawSize = 10 + 3 * 2 + 3 * 2;
|
||||
ASSERT_EQ(out.size(), drawSize * 2 + sizeof(bpCmd));
|
||||
EXPECT_EQ(out[0], GX_AURORA);
|
||||
EXPECT_EQ(std::memcmp(out.data() + drawSize, bpCmd, sizeof(bpCmd)), 0);
|
||||
EXPECT_EQ(out[drawSize + sizeof(bpCmd)], GX_AURORA);
|
||||
|
||||
// Re-walking the optimized list yields DrawIndexed commands with the same vertices
|
||||
Reader reader{out.data(), static_cast<u32>(out.size()), kPosClrDesc};
|
||||
auto cmd = reader.next();
|
||||
ASSERT_TRUE(cmd.has_value());
|
||||
ASSERT_EQ(cmd->kind, Command::Kind::DrawIndexed);
|
||||
EXPECT_EQ(cmd->draw.vtxCount, 3);
|
||||
EXPECT_EQ(cmd->draw.indexCount, 3u);
|
||||
EXPECT_EQ(cmd->draw.index(2), 2);
|
||||
EXPECT_EQ(cmd->draw.attr_idx(1, GX_VA_POS), 1);
|
||||
cmd = reader.next();
|
||||
ASSERT_TRUE(cmd.has_value());
|
||||
EXPECT_EQ(cmd->kind, Command::Kind::Passthrough);
|
||||
cmd = reader.next();
|
||||
ASSERT_TRUE(cmd.has_value());
|
||||
ASSERT_EQ(cmd->kind, Command::Kind::DrawIndexed);
|
||||
EXPECT_EQ(cmd->draw.attr_idx(0, GX_VA_POS), 3);
|
||||
EXPECT_FALSE(reader.failed());
|
||||
}
|
||||
|
||||
TEST(GXDlOptimize, FailsOnDirectAttrWithoutFmt) {
|
||||
const GXVtxDescList desc[] = {
|
||||
{GX_VA_POS, GX_DIRECT},
|
||||
{GX_VA_NULL, GX_NONE},
|
||||
};
|
||||
std::vector<u8> dl;
|
||||
draw_cmd(dl, op(GX_TRIANGLES, GX_VTXFMT0), 3, {0, 0, 0, 0, 0, 0});
|
||||
EXPECT_FALSE(optimize(dl.data(), static_cast<u32>(dl.size()), desc).has_value());
|
||||
}
|
||||
|
||||
TEST(GXDlOptimize, DirectAttrWithFmt) {
|
||||
const GXVtxDescList desc[] = {
|
||||
{GX_VA_PNMTXIDX, GX_DIRECT},
|
||||
{GX_VA_POS, GX_DIRECT},
|
||||
{GX_VA_NULL, GX_NONE},
|
||||
};
|
||||
const GXVtxAttrFmtList fmt0[] = {
|
||||
{GX_VA_POS, GX_POS_XYZ, GX_S16, 0},
|
||||
{GX_VA_NULL, GX_POS_XYZ, GX_U8, 0},
|
||||
};
|
||||
const VtxFmtLists fmts{fmt0};
|
||||
|
||||
// Stride: 1 (pnmtxidx) + 6 (3x s16) = 7; quad of 4 vertices
|
||||
std::vector<u8> dl;
|
||||
dl.push_back(op(GX_QUADS, GX_VTXFMT0));
|
||||
be16(dl, 4);
|
||||
for (u8 v = 0; v < 4; v++) {
|
||||
dl.push_back(v * 3); // pnmtxidx
|
||||
for (int b = 0; b < 6; b++) {
|
||||
dl.push_back(v);
|
||||
}
|
||||
}
|
||||
|
||||
const auto result = optimize(dl.data(), static_cast<u32>(dl.size()), desc, &fmts);
|
||||
ASSERT_TRUE(result.has_value());
|
||||
// DRAW_INDEXED: 10-byte header, 6 u16 indices, 4 7-byte vertices
|
||||
ASSERT_EQ(result->size(), 10u + 6 * 2 + 4 * 7);
|
||||
|
||||
Reader reader{result->data(), static_cast<u32>(result->size()), desc, &fmts};
|
||||
const auto cmd = reader.next();
|
||||
ASSERT_TRUE(cmd.has_value());
|
||||
ASSERT_EQ(cmd->kind, Command::Kind::DrawIndexed);
|
||||
EXPECT_EQ(cmd->draw.layout->stride, 7);
|
||||
EXPECT_EQ(cmd->draw.attr_idx(2, GX_VA_PNMTXIDX), 6);
|
||||
}
|
||||
|
||||
TEST_F(GXFifoTest, DrawIndexed_RoundTripThroughProcessor) {
|
||||
std::vector<u8> dl;
|
||||
draw_cmd(dl, op(GX_TRIANGLESTRIP, GX_VTXFMT0), 4, {0, 0, 1, 1, 2, 2, 3, 3});
|
||||
draw_cmd(dl, op(GX_TRIANGLEFAN, GX_VTXFMT0), 4, {4, 4, 5, 5, 6, 6, 7, 7});
|
||||
|
||||
const auto result = optimize(dl.data(), static_cast<u32>(dl.size()), kPosClrDesc);
|
||||
ASSERT_TRUE(result.has_value());
|
||||
|
||||
// Match the optimizer's descriptor in runtime CP state
|
||||
gxState().vtxDesc[GX_VA_POS] = GX_INDEX8;
|
||||
gxState().vtxDesc[GX_VA_CLR0] = GX_INDEX8;
|
||||
|
||||
aurora::gfx::g_testDrawCount = 0;
|
||||
decode_fifo(*result);
|
||||
|
||||
EXPECT_EQ(aurora::gfx::g_testDrawCount, 1u);
|
||||
EXPECT_EQ(aurora::gfx::g_testLastDraw.vtxCount, 8u);
|
||||
EXPECT_EQ(aurora::gfx::g_testLastDraw.indexCount, 12u);
|
||||
EXPECT_EQ(aurora::gfx::g_testLastDraw.instanceCount, 1u);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user