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
+1
View File
@@ -43,6 +43,7 @@ struct GraphicContext {
uint32_t min_subgroup_size = 0;
uint32_t max_subgroup_size = 0;
uint32_t max_push_descriptors = 0;
uint32_t device_clock_period_ps = 0;
vk::DescriptorSetLayout image_heap_layout = nullptr;
vk::ShaderStageFlags required_subgroup_size_stages = {};
Common::Mutex queue_mutex;
@@ -487,7 +487,8 @@ struct PipelineCache::ProgramCache {
// signed-zero results; GLSL.std.450 alone does not order -0 below +0.
profile {.maintenance8 = graphics.maintenance8_enabled,
.native_float_min_max =
graphics.physical_device_properties.vendorID == 0x1002u} {
graphics.physical_device_properties.vendorID == 0x1002u,
.device_clock_period_ps = graphics.device_clock_period_ps} {
lookup_key.static_state.reserve(MaxStaticKeyWords);
}
~ProgramCache() {
@@ -24,6 +24,7 @@
#include <SDL3/SDL.h>
#include <SDL3/SDL_vulkan.h>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <fmt/format.h>
@@ -506,6 +507,13 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
maintenance8.pNext = supported_features2.pNext;
supported_features2.pNext = &maintenance8;
}
const bool shader_clock_extension =
HasExtension(device_extensions, VK_KHR_SHADER_CLOCK_EXTENSION_NAME);
vk::PhysicalDeviceShaderClockFeaturesKHR shader_clock {};
if (shader_clock_extension) {
shader_clock.pNext = supported_features2.pNext;
supported_features2.pNext = &shader_clock;
}
const bool image_atomic_int64_extension =
HasExtension(device_extensions, VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME);
vk::PhysicalDeviceShaderImageAtomicInt64FeaturesEXT image_atomic_int64 {};
@@ -593,6 +601,11 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
feedback_extensions && feedback_layout.attachmentFeedbackLoopLayout &&
feedback_dynamic.attachmentFeedbackLoopDynamicState;
graphics.maintenance8_enabled = maintenance8_extension && maintenance8.maintenance8;
// The device clock ticks at timestampPeriod nanoseconds.
if (shader_clock_extension && shader_clock.shaderDeviceClock) {
graphics.device_clock_period_ps = static_cast<uint32_t>(
std::lround(graphics.physical_device_properties.limits.timestampPeriod * 1000.0f));
}
graphics.conditional_rendering_enabled =
conditional_rendering_extension && conditional_rendering.conditionalRendering;
LOGF("Vulkan maintenance8 support: %s\n",
@@ -702,6 +715,10 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
maintenance8.pNext = const_cast<void*>(create_info.pNext);
create_info.pNext = &maintenance8;
}
if (graphics.device_clock_period_ps != 0u) {
shader_clock.pNext = const_cast<void*>(create_info.pNext);
create_info.pNext = &shader_clock;
}
if (graphics.conditional_rendering_enabled) {
conditional_rendering.pNext = const_cast<void*>(create_info.pNext);
create_info.pNext = &conditional_rendering;
@@ -1077,6 +1094,7 @@ void WindowContext::CreateVulkan() {
graphic_ctx.memory_budget_ext_enabled = true;
}
for (const auto* extension: {VK_KHR_MAINTENANCE_8_EXTENSION_NAME,
VK_KHR_SHADER_CLOCK_EXTENSION_NAME,
VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME,
VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME,
VK_EXT_MESH_SHADER_EXTENSION_NAME,
@@ -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;
}
@@ -1,11 +1,9 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
#include "graphics/shader/recompiler/frontend/decode/ImageOps.h"
#include <algorithm>
#include <array>
#include <atomic>
namespace Libs::Graphics::ShaderRecompiler::Frontend {
@@ -969,17 +967,9 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::S_BUFFER_LOAD_DWORDX4:
case Decoder::Opcode::S_BUFFER_LOAD_DWORDX8:
case Decoder::Opcode::S_BUFFER_LOAD_DWORDX16: return S_LOAD(inst, false);
case Decoder::Opcode::S_MEMREALTIME: {
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
if (!warned.test_and_set(std::memory_order_relaxed)) {
Log::WriteToConsoleAndLog(
"Warning: S_MEMREALTIME uses placeholder UINT64_MAX; real-time clock not implemented.\n");
}
for (uint32_t component = 0; component < 2; component++) {
WriteOperand(ScalarDestinationOperand(inst.dst, component), IR::Value(UINT32_MAX));
}
return;
}
case Decoder::Opcode::S_MEMREALTIME:
return WriteU32Pair(inst.dst,
ExtractU64(IR::U64(ir.Emit(IR::ValueOpcode::ReadRealtimeClock))));
case Decoder::Opcode::BUFFER_LOAD_UBYTE:
case Decoder::Opcode::BUFFER_LOAD_UBYTE_D16:
@@ -10,6 +10,7 @@ VALUE_OPCODE(ConditionRef, U1, U1)
VALUE_OPCODE(GetUserData, U32, ScalarReg)
VALUE_OPCODE(GetDispatchThreadExtent, U32, U32)
VALUE_OPCODE(GetShaderBase, U64)
VALUE_OPCODE(ReadRealtimeClock, U64)
VALUE_OPCODE(MeshDrawParameter, U32, U32)
VALUE_OPCODE(MeshAllocate, Void, U32)
VALUE_OPCODE(TessellationBase, U32, U32)