shader: read S_MEMREALTIME from the host device clock shifted to about 100 MHz

This commit is contained in:
Senaxx
2026-10-23 14:02:16 +02:00
committed by nmzik
parent 1ffaaa96b1
commit 77a8f30758
10 changed files with 104 additions and 30 deletions
@@ -13,6 +13,8 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv {
struct Profile {
bool maintenance8 = false; // Dynamic texel offsets on image samples.
bool native_float_min_max = false; // NMin/NMax give the guest's NaN and signed-zero results.
// OpReadClockKHR Device tick in picoseconds; 0 without shaderDeviceClock.
uint32_t device_clock_period_ps = 0;
};
inline constexpr Profile DefaultProfile {};
@@ -4,6 +4,7 @@
#include <algorithm>
#include <atomic>
#include <bit>
#include <optional>
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
@@ -881,6 +882,22 @@ uint32_t EmitGetShaderBase(ValueEmitContext& ctx) {
return ctx.Def(IR::Value(uint64_t {0}));
}
uint32_t EmitReadRealtimeClock(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto period = state.profile.device_clock_period_ps;
if (period == 0u) {
ctx.Fail(inst, "requires shaderDeviceClock");
}
state.builder.RequireExtension("SPV_KHR_shader_clock");
state.builder.RequireCapability(spv::CapabilityShaderClockKHR);
const auto clock =
Unary(state, spv::OpReadClockKHR, TypeU64(state), ConstantU32(state, spv::ScopeDevice));
// S_MEMREALTIME counts at 100 MHz. Shift the host clock by the most bits that keep its tick at
// most 15000 ps (2/3 of 100 MHz): 1 GHz shifts by 3 to 125 MHz; 100 MHz and slower stay.
const uint32_t shift = std::bit_width(7500u / period);
return EmitShiftRightLogical64(ctx, clock, IR::Value(shift));
}
void EmitUnreachable(ValueEmitContext& ctx, const IR::Inst& inst) {
ctx.Fail(inst, "must be lowered before SPIR-V emission");
}
@@ -240,6 +240,7 @@ uint32_t EmitGetAttributeF16(ValueEmitContext& ctx, const IR::Inst&
uint32_t EmitGetInterpolationParameter(ValueEmitContext& ctx, const IR::Inst& inst);
void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetShaderBase(ValueEmitContext& ctx);
uint32_t EmitReadRealtimeClock(ValueEmitContext& ctx, const IR::Inst& inst);
inline constexpr auto EmitGetSrtResource = EmitVoid;
inline constexpr auto EmitGetBufferResource = EmitGetSrtResource;
uint32_t EmitGetBufferResourceIndex(ValueEmitContext& ctx, const IR::Inst& inst);
@@ -235,9 +235,9 @@ void EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto op = inst.GetOpcode();
if (ctx.half != 0 &&
(op == Op::Ballot || op == Op::ReadFirstLane || op == Op::SubgroupUMinU32 ||
op == Op::ReadConstBuffer ||
op == Op::ReadConstBuffer || op == Op::ReadRealtimeClock ||
(op == Op::LoadAddressU32 && ctx.Memory(inst).kind == IR::ResourceKind::ScalarAddress))) {
// Whole-wave results and SMEM loads (SGPR operands, no EXEC) are identical in both halves.
// Whole-wave results, clocks and SMEM loads (SGPR operands, no EXEC) match in both halves.
ctx.Define(inst, ctx.other_half->Result(inst));
return;
}