diff --git a/include/dolphin/gx/GXAurora.h b/include/dolphin/gx/GXAurora.h index f743515..52f1372 100644 --- a/include/dolphin/gx/GXAurora.h +++ b/include/dolphin/gx/GXAurora.h @@ -61,6 +61,8 @@ extern "C" { #define GX_LOAD_AURORA_DESTROY_COPY_TEX 0x0034 +#define GX2_SET_POLYGON_OFFSET 0x1000 + /* * Debug marker stuff @@ -111,6 +113,8 @@ void GXSetViewportRender(f32 left, f32 top, f32 wd, f32 ht, f32 nearz, f32 farz) */ void GXSetScissorRender(u32 left, u32 top, u32 wd, u32 ht); +void GX2SetPolygonOffset(f32 mFrontOffset, f32 mFrontScale, f32 mBackOffset, f32 mBackScale, f32 mClamp); + /** * Create an offscreen framebuffer and switch rendering to it. * All subsequent GX rendering will target this framebuffer until GXRestoreFrameBuffer() is called. diff --git a/lib/dolphin/gx/GXAurora.cpp b/lib/dolphin/gx/GXAurora.cpp index a82fbef..833ed96 100644 --- a/lib/dolphin/gx/GXAurora.cpp +++ b/lib/dolphin/gx/GXAurora.cpp @@ -66,6 +66,15 @@ void GXSetScissorRender(u32 left, u32 top, u32 wd, u32 ht) { GX_WRITE_U32(ht); } +void GX2SetPolygonOffset(f32 mFrontOffset, f32 mFrontScale, f32 mBackOffset, f32 mBackScale, f32 mClamp) { + GX_WRITE_AURORA(GX2_SET_POLYGON_OFFSET); + GX_WRITE_F32(mFrontOffset); + GX_WRITE_F32(mFrontScale); + GX_WRITE_F32(mBackOffset); + GX_WRITE_F32(mBackScale); + GX_WRITE_F32(mClamp); +} + void GXCreateFrameBuffer(u32 width, u32 height) { aurora::gx::fifo::drain(); aurora::gfx::begin_offscreen(width, height); diff --git a/lib/gx/command_processor.cpp b/lib/gx/command_processor.cpp index a33c000..cdf49a8 100644 --- a/lib/gx/command_processor.cpp +++ b/lib/gx/command_processor.cpp @@ -1765,6 +1765,19 @@ 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 == GX2_SET_POLYGON_OFFSET) { + CHECK(pos + 20 <= size, "GX2_SET_POLYGON_OFFSET read overrun"); + g_gxState.frontOffset = read_f32(data + pos, bigEndian); + pos += 4; + g_gxState.frontScale = read_f32(data + pos, bigEndian); + pos += 4; + g_gxState.backOffset = read_f32(data + pos, bigEndian); + pos += 4; + g_gxState.backScale = read_f32(data + pos, bigEndian); + pos += 4; + 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"); evict_texture_object(read_u32(data + pos, bigEndian)); diff --git a/lib/gx/gx.cpp b/lib/gx/gx.cpp index ad52935..52d0cbf 100644 --- a/lib/gx/gx.cpp +++ b/lib/gx/gx.cpp @@ -16,9 +16,12 @@ #include #include +#include #include +#include #include #include +#include static aurora::Module Log("aurora::gx"); @@ -179,9 +182,8 @@ gfx::TextureHandle resolve_static_texture(const GXTexObj_& obj) { #else const auto nameStr = "GX Static Texture"; #endif - handle = - gfx::new_static_texture_2d(obj.width(), obj.height(), obj.mip_count(), obj.format(), - {static_cast(obj.data), UINT32_MAX}, false, nameStr); + handle = gfx::new_static_texture_2d(obj.width(), obj.height(), obj.mip_count(), obj.format(), + {static_cast(obj.data), UINT32_MAX}, false, nameStr); } if (!obj.no_cache()) { store_cached_texture(obj, handle); @@ -261,6 +263,18 @@ u32 resolved_format_for_handle(const gfx::TextureHandle& handle) { } return GX_TF_RGBA8_PC; } + +template +T round_away_from_zero(float value) noexcept { + return static_cast(value < 0.0f ? std::floor(value) : std::ceil(value)); +} + +std::pair polygon_offset_for_cull_mode(GXCullMode cullMode) noexcept { + if (cullMode == GX_CULL_FRONT) { + return {g_gxState.backOffset, g_gxState.backScale}; + } + return {g_gxState.frontOffset, g_gxState.frontScale}; +} } // namespace Vec2 logical_fb_size() noexcept { @@ -384,9 +398,7 @@ void clear_copy_texture_cache() noexcept { } } -void clear_static_texture_cache() noexcept { - s_staticTextureCacheClearPending.store(true, std::memory_order_release); -} +void clear_static_texture_cache() noexcept { s_staticTextureCacheClearPending.store(true, std::memory_order_release); } void evict_copy_texture(const void* dest) noexcept { g_gxState.copyTextures.erase(dest); @@ -597,10 +609,15 @@ static inline wgpu::PrimitiveState to_primitive_state(GXCullMode gx_cullMode) { wgpu::RenderPipeline build_pipeline(const PipelineConfig& config, ArrayRef vtxBuffers, wgpu::ShaderModule shader, const char* label) noexcept { ZoneScoped; + const float depthBias = (UseReversedZ ? -1.0f : 1.0f) * std::bit_cast(config.polygonOffsetBits); + const float depthBiasSlopeScale = (UseReversedZ ? -1.0f : 1.0f) * std::bit_cast(config.polygonOffsetScaleBits); const wgpu::DepthStencilState depthStencil{ .format = g_graphicsConfig.depthFormat, .depthWriteEnabled = config.depthCompare && config.depthUpdate, .depthCompare = config.depthCompare ? to_compare_function(config.depthFunc) : wgpu::CompareFunction::Always, + .depthBias = round_away_from_zero(depthBias), + .depthBiasSlopeScale = depthBiasSlopeScale, + .depthBiasClamp = std::bit_cast(config.polygonOffsetClampBits), }; const auto blendState = to_blend_state(config.blendMode, config.blendFacSrc, config.blendFacDst, config.blendOp, config.dstAlpha); @@ -813,16 +830,21 @@ void populate_pipeline_config(PipelineConfig& config, GXPrimitive primitive, GXV if (g_gxState.alphaCompare) { config.shaderConfig.alphaCompare = g_gxState.alphaCompare; } + const auto cullMode = config.shaderConfig.lineMode == 0 ? g_gxState.cullMode : GX_CULL_NONE; + const auto [polygonOffset, polygonOffsetScale] = polygon_offset_for_cull_mode(cullMode); config = { .msaaSamples = gfx::get_sample_count(), .shaderConfig = config.shaderConfig, .depthFunc = g_gxState.depthFunc, - .cullMode = config.shaderConfig.lineMode == 0 ? g_gxState.cullMode : GX_CULL_NONE, + .cullMode = cullMode, .blendMode = g_gxState.blendMode, .blendFacSrc = g_gxState.blendFacSrc, .blendFacDst = g_gxState.blendFacDst, .blendOp = g_gxState.blendOp, .dstAlpha = g_gxState.dstAlpha, + .polygonOffsetBits = std::bit_cast(polygonOffset), + .polygonOffsetScaleBits = std::bit_cast(polygonOffsetScale), + .polygonOffsetClampBits = std::bit_cast(g_gxState.clamp), .depthCompare = g_gxState.depthCompare, .depthUpdate = g_gxState.depthUpdate, .alphaUpdate = g_gxState.alphaUpdate, diff --git a/lib/gx/gx.hpp b/lib/gx/gx.hpp index d747894..5473c1e 100644 --- a/lib/gx/gx.hpp +++ b/lib/gx/gx.hpp @@ -380,6 +380,13 @@ struct GXState { }(); std::array xfRegCache; + // GX2 state + f32 frontOffset = 0.0f; + f32 frontScale = 0.0f; + f32 backOffset = 0.0f; + f32 backScale = 0.0f; + f32 clamp = 0.0f; + void clearVtxSizeCache() { lastVtxFmt = GX_MAX_VTXFMT; } }; extern GXState g_gxState; diff --git a/lib/gx/pipeline.hpp b/lib/gx/pipeline.hpp index e1e652e..474c85c 100644 --- a/lib/gx/pipeline.hpp +++ b/lib/gx/pipeline.hpp @@ -16,7 +16,7 @@ struct DrawData { uint32_t dstAlpha; }; -constexpr uint32_t GXPipelineConfigVersion = 11; +constexpr uint32_t GXPipelineConfigVersion = 13; struct PipelineConfig { uint32_t version = GXPipelineConfigVersion; uint32_t msaaSamples = 1; @@ -27,6 +27,9 @@ struct PipelineConfig { GXBlendFactor blendFacSrc, blendFacDst; GXLogicOp blendOp; uint32_t dstAlpha; + uint32_t polygonOffsetBits; + uint32_t polygonOffsetScaleBits; + uint32_t polygonOffsetClampBits; bool depthCompare, depthUpdate, alphaUpdate, colorUpdate; }; static_assert(std::has_unique_object_representations_v);