From 5be3988233d94e94630ee136f663490b0b032c44 Mon Sep 17 00:00:00 2001 From: Joshua Warren <816217+joshuaswarren@users.noreply.github.com> Date: Wed, 11 Nov 2026 17:53:26 -0600 Subject: [PATCH] agx: keep the f32-to-f16 move after persp_mul_agx3 on AGX3C A mediump varying interpolated with the native perspective path is emitted as persp_mul_agx3 into a 32-bit register followed by an f32 to f16 move. The optimizer folded that move into the multiply and gave it a 16-bit destination. agx3_persp_mul_encode only encodes a 32-bit destination, so the binary packer refused the instruction ("AGX3 binary packer refused persp_mul_agx3") and the draw was skipped. On an M5 Max (G17C) the glmark2 terrain scene ran at 2 FPS because of it. Skip the fold when the producer is persp_mul_agx3 with a 32-bit destination and the move narrows to 16 bits on the AGX3C encoding. The AGX3B encoding already refuses every producer except FMUL, so it is not affected. Add an optimizer test that builds the multiply plus narrowing move for G17C and checks the pair is left alone. It fails without the change and the whole agx_tests suite passes with it. Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com> --- src/asahi/compiler/agx_optimizer.c | 9 ++++++++ src/asahi/compiler/test/test-optimizer.cpp | 25 ++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/asahi/compiler/agx_optimizer.c b/src/asahi/compiler/agx_optimizer.c index 1df093b72249..ff2ac82e865c 100644 --- a/src/asahi/compiler/agx_optimizer.c +++ b/src/asahi/compiler/agx_optimizer.c @@ -616,6 +616,15 @@ agx_optimizer_fmov_rev(agx_context *ctx, agx_instr *I, agx_instr *use) (I->src[0].abs || I->src[0].neg || I->src[1].abs || I->src[1].neg)) return false; + /* M5 (AGX3C): the native perspective multiply only encodes a word + * destination (agx3_persp_mul_encode). Folding the f32-to-f16 move that + * narrows a mediump varying into it made the binary packer refuse the + * shader and the draw was skipped. Keep the explicit move. */ + if (ctx->key && ctx->key->dev.encoding == AGX_ISA_ENCODING_AGX3C && + I->op == AGX_OPCODE_PERSP_MUL_AGX3 && + I->dest[0].size == AGX_SIZE_32 && use->dest[0].size == AGX_SIZE_16) + return false; + /* saturate(saturate(x)) = saturate(x) */ I->saturate |= use->saturate; I->dest[0] = use->dest[0]; diff --git a/src/asahi/compiler/test/test-optimizer.cpp b/src/asahi/compiler/test/test-optimizer.cpp index 0bd63a55091b..2317b594ae89 100644 --- a/src/asahi/compiler/test/test-optimizer.cpp +++ b/src/asahi/compiler/test/test-optimizer.cpp @@ -204,6 +204,31 @@ TEST_F(Optimizer, G17PPreservesHalfWidenBeforeFltnorm) ASSERT_SHADER_EQUAL(actual->shader, expected->shader); } +/* + * The native projective f32mul (PERSP_MUL) has no 16-bit destination form. A + * mediump varying narrows its product with an f32 to f16 move; folding that + * move into the multiply made the shader unencodable on M5 (AGX3C), so the + * move stays a separate instruction. + */ +TEST_F(Optimizer, G17CKeepsPerspMulNarrowingMove) +{ + agx_builder *actual = agx_test_builder(mem_ctx); + agx_builder *expected = agx_test_builder(mem_ctx); + struct agx_shader_key key = {}; + key.dev.encoding = AGX_ISA_ENCODING_AGX3C; + key.dev.chip = AGX_CHIP_G17C; + for (agx_builder *b : {actual, expected}) { + b->shader->key = &key; + agx_index product = agx_temp(b->shader, AGX_SIZE_32); + agx_persp_mul_agx3_to(b, product, wx, wy, 1); + agx_index narrow = agx_temp(b->shader, AGX_SIZE_16); + agx_fmov_to(b, narrow, product); + agx_unit_test(b, narrow); + } + agx_optimize_and_dce(actual->shader); + ASSERT_SHADER_EQUAL(actual->shader, expected->shader); +} + /* * AGX3 has no select form without a register compare operand, so a select of * two immediates -- here fused out of a compare of two constants -- has to