Compare commits

..
Author SHA1 Message Date
nmzik 7b5a33f878 Fix PS5 front-face VGPR encoding to restore scene lighting 2026-09-15 07:01:10 +04:00
nmzik ea092a9472 Fix DCC clears through aliased image formats
Clear DCC images using the requested view format.
UNORM alpha 0xffff reads as NaN in a FLOAT view.
2026-09-15 07:01:10 +04:00
nmzikandBipin 18a1e0fadc Remove global condition waiter registry that deadlocks exception delivery
Co-authored-by: Bipin <bipinkrish4@gmail.com>
2026-09-14 21:34:01 +04:00
nmzik 437e69ef1b Remove extra DCC tracking 2026-09-14 20:29:20 +04:00
nmzik dd408ffd00 Resolve image aliases before buffer uploads 2026-09-14 20:29:20 +04:00
nmzik bce8924dd6 Use dynamic stencil state 2026-09-14 20:29:20 +04:00
nmzik fe26c1de17 Share SPIR-V module creation 2026-09-14 20:29:20 +04:00
nmzik 01df42a5f3 Fix DCC clears after metadata writes 2026-09-14 20:29:20 +04:00
nmzik 968cf3c03f Use the correct pixel shader wave size 2026-09-14 20:29:20 +04:00
nmzik 586cbd4a0d Fix separate stencil test and write values 2026-09-14 20:29:20 +04:00
nmzik 940e42f130 Share stencil setup for both faces 2026-09-14 20:29:20 +04:00
nmzik 3f80151a6f Respect vertex buffer sizes 2026-09-14 20:29:20 +04:00
nmzik 7e89b97daf Handle front shader allocation state 2026-09-14 20:29:20 +04:00
nmzik d63fb62367 Fix async compute completion interrupts 2026-09-14 20:29:20 +04:00
nmzik 29e9ea6344 Fix masked stencil replacements 2026-09-14 20:29:20 +04:00
nmzik 9a74ef15c3 Move stencil checks into op conversion 2026-09-14 20:29:20 +04:00
nmzikandBlackCAT a9ae04c152 graphics: decode observed FSR setup registers
Inspired by https://github.com/KytyPS5/KytyPS5/pull/607

Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
2026-09-14 10:30:28 +04:00
nmzikandBlackCAT 8e35e1233b hmd2: initialize reprojection and virtual headset field of view
Inspired by https://github.com/KytyPS5/KytyPS5/pull/607

Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
2026-09-14 10:30:28 +04:00
nmzikandBlackCAT 5610976720 videoout: map buses to dense port slots before opening
Inspired by https://github.com/KytyPS5/KytyPS5/pull/607

Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
2026-09-14 10:30:28 +04:00
nmzikandBlackCAT 1133272668 hmd2: initialize a virtual headset with --vr
Inspired by https://github.com/KytyPS5/KytyPS5/pull/607

Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
2026-09-14 10:30:28 +04:00
nmzik bbebb6419b graphics: preserve PS5 array mip contents during cache promotion
PPSA05975
2026-09-14 06:58:32 +04:00
nmzik b67cb81359 avplayer: report loop boundaries when decoded output is delivered 2026-09-14 06:58:32 +04:00
nmzikandBlackCAT f3c82a317a kernel: support timed umtx waits through shared address deadlines
Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
2026-09-14 06:58:32 +04:00
nmzik f426d60ff1 net: use the shared host resolver for Linux name lookups 2026-09-14 06:58:32 +04:00
nmzikandAli Almohaya b2fdad9e47 memory: preserve live POSIX mappings during partial unmap
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
2026-09-14 06:58:32 +04:00
nmzik 87971a28f5 gpu: isolate tessellation analysis and SPIR-V emission 2026-09-14 06:58:32 +04:00
nmzik 96d62b3367 gpu: reflect tessellation strides from memory address dataflow 2026-09-14 06:58:32 +04:00
nmzik 7516068662 gpu: compile RDNA2 tessellation through native LS HS and TES stages 2026-09-14 06:58:32 +04:00
nmzik 6d1ba5870a renderer: share graphics resource preparation across shader stages 2026-09-14 06:58:32 +04:00
nmzik 00a16f3120 gpu: retain fused hull shader user-data addresses 2026-09-14 06:58:32 +04:00
nmzik d3d7bd33f8 launcher: Add game size column 2026-09-12 17:56:44 +04:00
Mehmet Cambaz 069f928edc net: return copied bytes for truncated datagram receives (#510)
Normalize Winsock WSAEMSGSIZE on receive to the copied byte count before converting the peer address. Preserve other receive errors and oversized send failures.
2026-09-12 17:51:43 +04:00
Mininautandnmzik 0f3674f531 kernel: release descriptors when rejecting read-only truncation (#504)
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-12 17:44:45 +04:00
ADBHXNLOLandnmzik a21ffde34e shader: decode and translate V_FRACT_F16 (VOP1 0x5f) and V_CMPX_LT_U16 (VOPC 0xb9) (#582)
shader: support V_FRACT_F16 and V_CMPX_LT_U16

Decode compact, SDWA, and VOP3 forms using the existing arithmetic and comparison paths. Preserve EXEC masking and VCC for the integer compare, and reject unsupported DPP encoding.

Add captured-instruction, operand-selection, floating-point edge, and execution-mask regression coverage.

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-12 17:21:32 +04:00
nmzik 1208d22547 Fix Windows Clang SPIR-V serialization with typed operands 2026-09-12 13:08:23 +02:00
nmzik 3a27f8a76a build: fix SPIR-V enum word conversions 2026-09-12 15:43:47 +04:00
ADBHXNLOL dff2b19c56 json2: export Value::clear (#584)
Export the existing clear implementation while preserving tree ownership and root handler metadata.
2026-09-12 15:15:26 +04:00
85 changed files with 4633 additions and 2002 deletions

No files matched your search

+4
View File
@@ -55,6 +55,10 @@ bool FullscreenEnabled() {
return g_config->fullscreen_enabled;
}
bool VrEnabled() {
return g_config->vr_enabled;
}
uint32_t GetVblankFrequency() {
return std::clamp(g_config->vblank_frequency, 30u, 360u);
}
+2
View File
@@ -46,6 +46,7 @@ struct ConfigOptions {
PresentMode present_mode = PresentMode::Mailbox;
int32_t gpu_index = -1;
bool fullscreen_enabled = false;
bool vr_enabled = false;
uint32_t vblank_frequency = 60;
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
bool vulkan_validation_enabled = false;
@@ -79,6 +80,7 @@ int32_t GetUserId();
PresentMode GetPresentMode();
int32_t GetGpuIndex();
bool FullscreenEnabled();
bool VrEnabled();
uint32_t GetVblankFrequency();
uint32_t GetConsoleLanguage();
bool VulkanValidationEnabled();
+8 -46
View File
@@ -8,7 +8,6 @@
#include <chrono> // IWYU pragma: keep
#include <condition_variable> // IWYU pragma: keep
#include <mutex>
#include <vector>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_COMPILER == KYTY_COMPILER_CLANG
#define KYTY_WIN_CS
@@ -197,37 +196,7 @@ struct CondVarPrivate {
#endif
};
static std::recursive_mutex g_cond_waiters_mutex;
static std::vector<std::pair<int, CondVarPrivate*>> g_cond_waiters;
static wait_poll_func_t g_cond_wait_poll_callback = nullptr;
static void WakeCondVar(CondVarPrivate* cond_var) {
#ifdef KYTY_WIN_CS
static auto func = ResolveWakeAllConditionVariable();
EXIT_NOT_IMPLEMENTED(func == nullptr);
func(&cond_var->m_cv);
#else
cond_var->m_cv.notify_all();
#endif
}
static void RegisterCondWaiter(CondVarPrivate* cond_var) {
std::lock_guard lock(g_cond_waiters_mutex);
g_cond_waiters.emplace_back(Thread::GetThreadIdUnique(), cond_var);
}
static void UnregisterCondWaiter(CondVarPrivate* cond_var) {
const auto thread_id = Thread::GetThreadIdUnique();
std::lock_guard lock(g_cond_waiters_mutex);
const auto it = std::find_if(g_cond_waiters.begin(), g_cond_waiters.end(),
[thread_id, cond_var](const auto& waiter) {
return waiter.first == thread_id && waiter.second == cond_var;
});
if (it != g_cond_waiters.end()) {
g_cond_waiters.erase(it);
}
}
static wait_poll_func_t g_cond_wait_poll_callback = nullptr;
struct ThreadPrivate {
ThreadPrivate(thread_func_t f, void* a): func(f), arg(a), m_thread(&Run, this) {}
@@ -365,7 +334,6 @@ CondVar::~CondVar() {
}
void CondVar::Wait(Mutex* mutex) {
RegisterCondWaiter(m_cond_var.get());
#ifndef KYTY_WIN_CS
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
#endif
@@ -406,7 +374,6 @@ void CondVar::Wait(Mutex* mutex) {
}
cpp_lock.release();
#endif
UnregisterCondWaiter(m_cond_var.get());
}
void CondVar::SetWaitPollCallback(wait_poll_func_t callback) {
@@ -415,7 +382,6 @@ void CondVar::SetWaitPollCallback(wait_poll_func_t callback) {
bool CondVar::WaitFor(Mutex* mutex, uint32_t micros) {
bool ok = false;
RegisterCondWaiter(m_cond_var.get());
#ifndef KYTY_WIN_CS
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
#endif
@@ -430,7 +396,6 @@ bool CondVar::WaitFor(Mutex* mutex, uint32_t micros) {
std::cv_status::no_timeout);
cpp_lock.release();
#endif
UnregisterCondWaiter(m_cond_var.get());
return ok;
}
@@ -445,16 +410,13 @@ void CondVar::Signal() {
}
void CondVar::SignalAll() {
WakeCondVar(m_cond_var.get());
}
void CondVar::SignalThread(int thread_id) {
std::lock_guard lock(g_cond_waiters_mutex);
for (const auto& waiter: g_cond_waiters) {
if (waiter.first == thread_id) {
WakeCondVar(waiter.second);
}
}
#ifdef KYTY_WIN_CS
static auto func = ResolveWakeAllConditionVariable();
EXIT_NOT_IMPLEMENTED(func == nullptr);
func(&m_cond_var->m_cv);
#else
m_cond_var->m_cv.notify_all();
#endif
}
int Thread::GetThreadIdUnique() {
-1
View File
@@ -77,7 +77,6 @@ public:
void Signal();
void SignalAll();
static void SignalThread(int thread_id);
static void SetWaitPollCallback(wait_poll_func_t callback);
KYTY_CLASS_NO_COPY(CondVar);
@@ -3635,6 +3635,11 @@ void GraphicsInitJmpTablesShIndirect() {
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_RSRC4_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
// Toolkit state restoration emits the compiler's GS-front allocation
// metadata here. Native GS resource registers already carry that allocation.
g_hw_sh_indirect_func[0x0ca] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
@@ -3654,12 +3659,13 @@ void GraphicsInitJmpTablesShIndirect() {
g_hw_sh_indirect_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
for (uint32_t offset = Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS;
offset <= Pm4::SPI_SHADER_USER_DATA_ADDR_HI_HS; offset++) {
g_hw_sh_indirect_func[offset] = [](KYTY_HW_SH_INDIRECT_ARGS) {
cp.GetShCtx().SetHsUserDataAddress(cmd_offset - Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS,
value);
};
}
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_LO_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
auto base = cp.GetShCtx().GetVs().hs_regs.data_addr;
base &= 0xFFFFFF00000000FFull;
@@ -3818,6 +3824,25 @@ void GraphicsInitJmpTablesUcIndirect() {
for (auto& func: g_hw_uc_indirect_func) {
func = nullptr;
}
for (uint32_t i = 0; i < 4; ++i) {
g_hw_uc_indirect_func[Pm4::FSR_CONTROL_POINTS_LEFT_X + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
cp.GetUcfg().SetFsrControlPoint(0, cmd_offset - Pm4::FSR_CONTROL_POINTS_LEFT_X, value);
};
g_hw_uc_indirect_func[Pm4::FSR_CONTROL_POINTS_LEFT_Y + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
cp.GetUcfg().SetFsrControlPoint(1, cmd_offset - Pm4::FSR_CONTROL_POINTS_LEFT_Y, value);
};
}
for (uint32_t i = 0; i < 2; ++i) {
g_hw_uc_indirect_func[Pm4::FSR_ALPHA_LEFT_X + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
cp.GetUcfg().SetFsrAlpha(0, cmd_offset - Pm4::FSR_ALPHA_LEFT_X, value);
};
g_hw_uc_indirect_func[Pm4::FSR_ALPHA_LEFT_Y + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
cp.GetUcfg().SetFsrAlpha(1, cmd_offset - Pm4::FSR_ALPHA_LEFT_Y, value);
};
g_hw_uc_indirect_func[Pm4::FSR_WINDOW_LEFT + i] = [](KYTY_HW_UC_INDIRECT_ARGS) {
cp.GetUcfg().SetFsrWindow(cmd_offset - Pm4::FSR_WINDOW_LEFT, value);
};
}
g_hw_uc_indirect_func[Pm4::GE_CNTL] = [](KYTY_HW_UC_INDIRECT_ARGS) {
HW::GeControl r;
+1 -1
View File
@@ -1292,6 +1292,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
break;
case 0x14:
case 0x28:
case 0x2f:
if (event_index == 0x00) {
write64(false);
return;
@@ -1299,7 +1300,6 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
break;
case 0x2b:
case 0x2d:
case 0x2f:
case 0x30:
if (event_index == 0x00 && !with_interrupt) {
write64(false);
+27 -5
View File
@@ -498,7 +498,8 @@ struct LsStageRegisters {
};
struct HsStageRegisters {
uint64_t data_addr = 0;
uint64_t data_addr = 0;
uint64_t user_data_addr = 0;
HsShaderResource1 rsrc1;
HsShaderResource2 rsrc2;
};
@@ -935,6 +936,12 @@ private:
ShaderRegisters m_sh_regs;
};
struct FsrView {
uint32_t control_points[2][4] {};
uint32_t alphas[2][2] {};
uint32_t window[2] {};
};
class UserConfig {
public:
UserConfig() = default;
@@ -964,8 +971,17 @@ public:
void SetGdsOaCntl(uint32_t value) { m_gds_oa.cntl = value; }
void SetGdsOaCounter(uint32_t value) { m_gds_oa.counters[m_gds_oa.GetIndex()].counter = value; }
void SetGdsOaAddress(uint32_t value) { m_gds_oa.counters[m_gds_oa.GetIndex()].address = value; }
void SetFsrControlPoint(uint32_t axis, uint32_t index, uint32_t value) {
m_fsr_view.control_points[axis][index] = value;
}
void SetFsrAlpha(uint32_t axis, uint32_t index, uint32_t value) {
m_fsr_view.alphas[axis][index] = value;
}
void SetFsrWindow(uint32_t index, uint32_t value) { m_fsr_view.window[index] = value; }
[[nodiscard]] const FsrView& GetFsrView() const { return m_fsr_view; }
private:
FsrView m_fsr_view;
Prospero::PrimitiveType m_prim_type = Prospero::PrimitiveType::kNone;
uint32_t m_index_offset = 0;
uint32_t m_object_id = 0;
@@ -988,14 +1004,14 @@ public:
void SetEsShaderBase(uint64_t addr) { m_vs.es_regs.data_addr = addr; }
void SetLsShaderBase(uint64_t addr) { m_vs.ls_regs.data_addr = addr; }
void SetHsShaderBase(uint64_t addr) { m_vs.hs_regs.data_addr = addr; }
void SetHsUserDataAddress(uint32_t word, uint32_t value) {
SetUserDataAddressWord(m_vs.hs_regs.user_data_addr, word, value);
}
void SetHsShaderResource1(const HsShaderResource1& rsrc1) { m_vs.hs_regs.rsrc1 = rsrc1; }
void SetHsShaderResource2(const HsShaderResource2& rsrc2) { m_vs.hs_regs.rsrc2 = rsrc2; }
void SetGsShaderBase(uint64_t addr) { m_vs.gs_regs.data_addr = addr; }
void SetGsUserDataAddress(uint32_t word, uint32_t value) {
const auto shift = word * 32u;
auto& address = m_vs.gs_regs.user_data_addr;
address = (address & ~(uint64_t {0xffffffffu} << shift)) |
(static_cast<uint64_t>(value) << shift);
SetUserDataAddressWord(m_vs.gs_regs.user_data_addr, word, value);
}
void SetGsShaderResource1(const GsShaderResource1& rsrc1) { m_vs.gs_regs.rsrc1 = rsrc1; }
void SetGsShaderResource2(const GsShaderResource2& rsrc2) { m_vs.gs_regs.rsrc2 = rsrc2; }
@@ -1037,6 +1053,12 @@ public:
[[nodiscard]] const ComputeShaderInfo& GetCs() const { return m_cs; }
private:
static void SetUserDataAddressWord(uint64_t& address, uint32_t word, uint32_t value) {
const auto shift = word * 32u;
address = (address & ~(uint64_t {0xffffffffu} << shift)) |
(static_cast<uint64_t>(value) << shift);
}
VertexShaderInfo m_vs;
PixelShaderInfo m_ps;
ComputeShaderInfo m_cs;
+6
View File
@@ -987,6 +987,12 @@ constexpr uint32_t GE_USER_VGPR_EN_EN_USER_VGPR2_MASK = 0x1;
constexpr uint32_t GE_USER_VGPR_EN_EN_USER_VGPR3_SHIFT = 2;
constexpr uint32_t GE_USER_VGPR_EN_EN_USER_VGPR3_MASK = 0x1;
constexpr uint32_t FSR_WINDOW_LEFT = 0x2BC;
constexpr uint32_t FSR_CONTROL_POINTS_LEFT_X = 0x2C4;
constexpr uint32_t FSR_CONTROL_POINTS_LEFT_Y = 0x2CC;
constexpr uint32_t FSR_ALPHA_LEFT_X = 0x2E4;
constexpr uint32_t FSR_ALPHA_LEFT_Y = 0x2E8;
constexpr uint32_t TA_CS_BC_BASE_ADDR = 0x380;
constexpr uint32_t TA_CS_BC_BASE_ADDR_HI = 0x381;
constexpr uint32_t TEXTURE_GRADIENT_FACTORS = 0x383;
+1 -6
View File
@@ -44,12 +44,7 @@ FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler
&m_fault_process_desc_layout),
"create fault-buffer descriptor layout");
vk::ShaderModuleCreateInfo module_info {};
module_info.codeSize = std::size(FAULT_BUFFER_PROCESS_SPV) * sizeof(uint32_t);
module_info.pCode = FAULT_BUFFER_PROCESS_SPV;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
"create fault-buffer shader module");
const auto module = CompileSPV(FAULT_BUFFER_PROCESS_SPV, m_graphics.device);
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
pipeline_layout_info.setLayoutCount = 1;
+115 -153
View File
@@ -33,22 +33,24 @@ namespace {
constexpr uint64_t NumFramesBeforeRemoval = 32;
[[nodiscard]] bool DecodeDccClear(const TextureCache::ImageDesc& desc, vk::Format format,
uint32_t fill, vk::ClearColorValue& clear) {
const auto code = static_cast<uint8_t>(fill);
if (fill != static_cast<uint32_t>(code) * 0x01010101u) {
return false;
[[nodiscard]] bool DecodeDccClear(const TextureCache::ImageDesc& desc, uint8_t code,
vk::ClearColorValue& clear) {
switch (code) {
case 0x00:
case 0x20:
case 0x40:
case 0x80:
case 0xc0: break;
default: return false;
}
const auto& metadata = desc.info.metadata;
const auto format = desc.view_info.format;
if (code == 0x20) {
// Clear-to-register is a color-buffer operation; the texture pipe cannot decode it.
return desc.type == TextureCache::BindingType::RenderTarget &&
metadata.dcc_clear_register_valid &&
DecodePackedColorClear(format, metadata.dcc_clear_word, clear);
}
if (code != 0x00 && code != 0x40 && code != 0x80 && code != 0xc0) {
return false;
}
clear = {};
if (code == 0x00) {
return true;
@@ -323,10 +325,8 @@ void TextureCache::DeleteImage(ImageId id) {
if (image->info.HasMetadata()) {
const auto metadata = m_surface_metas.find(image->info.metadata.range.address);
if (metadata != m_surface_metas.end() &&
((image->info.metadata.kind == ImageMetadataKind::Dcc &&
metadata->second.type == MetaDataInfo::Type::Dcc) ||
(image->info.metadata.kind == ImageMetadataKind::Htile &&
metadata->second.type == MetaDataInfo::Type::HTile))) {
image->info.metadata.kind == ImageMetadataKind::Htile &&
metadata->second.type == MetaDataInfo::Type::HTile) {
// A later binding may have reused this address for another metadata type.
m_surface_metas.erase(metadata);
}
@@ -810,17 +810,10 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
const bool safe_to_delete =
current_tick - std::min(current_tick, cached.tick_accessed_last) > NumFramesBeforeRemoval;
if (requested.data.address == cached.info.data.address) {
const uint32_t requested_block = requested.bytes_per_block * requested.samples;
const uint32_t cached_block = cached.info.bytes_per_block * cached.info.samples;
if (requested.BlockExtent() != cached.info.BlockExtent() ||
requested_block != cached_block) {
if (safe_to_delete) {
FreeImage(cached_id);
}
return {merged_id};
}
const uint32_t requested_block = requested.bytes_per_block * requested.samples;
const uint32_t cached_block = cached.info.bytes_per_block * cached.info.samples;
if (requested.data.address == cached.info.data.address &&
requested.BlockExtent() == cached.info.BlockExtent() && requested_block == cached_block) {
if (const auto depth_id = ResolveDepthOverlap(requested, binding, cached_id)) {
return {depth_id};
}
@@ -873,38 +866,27 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
static_cast<uint32_t>(cached.info.tile_mode));
}
if (requested.data.address > cached.info.data.address) {
const int32_t mip = requested.MipOf(cached.info);
if (mip >= 0) {
const int32_t layer = requested.SliceOf(cached.info, mip);
if (layer >= 0) {
return {cached_id, mip, layer};
}
}
if (safe_to_delete) {
FreeImage(cached_id);
}
return {};
const int32_t requested_mip = requested.MipOf(cached.info);
if (requested_mip >= 0) {
const int32_t layer = requested.SliceOf(cached.info, requested_mip);
return {cached_id, requested_mip, layer};
}
const int32_t mip = cached.info.MipOf(requested);
if (mip >= 0) {
const int32_t layer = cached.info.SliceOf(requested, mip);
if (layer >= 0) {
if (cached.binding.is_target) {
cached.binding.needs_rebind = true;
if (merged_id) {
m_slot_images[merged_id].binding.is_target = true;
}
FreeImage(cached_id);
return {merged_id};
}
if (merged_id) {
CopyImageMip(merged_id, cached_id, static_cast<uint32_t>(mip),
static_cast<uint32_t>(layer));
FreeImage(cached_id);
}
if (!merged_id) {
return {ExpandImage(requested, cached_id)};
}
cached.binding.needs_rebind |= cached.binding.is_bound || cached.binding.is_target;
m_slot_images[merged_id].binding.is_target |= cached.binding.is_target;
CopyImageMip(merged_id, cached_id, static_cast<uint32_t>(mip),
static_cast<uint32_t>(layer));
FreeImage(cached_id);
return {merged_id};
}
if (requested.data.address >= cached.info.data.address && safe_to_delete) {
FreeImage(cached_id);
}
return {merged_id};
}
@@ -919,7 +901,14 @@ ImageId TextureCache::ExpandImage(const ImageInfo& info, ImageId source_id) {
source.binding.needs_rebind = true;
}
InitializeImage(expanded_id);
CopyImage(expanded_id, source_id);
const int32_t mip = source.info.MipOf(info);
const int32_t layer = source.info.SliceOf(info, mip);
if (layer >= 0) {
CopyImageMip(expanded_id, source_id, static_cast<uint32_t>(mip),
static_cast<uint32_t>(layer));
} else {
CopyImage(expanded_id, source_id);
}
FreeImage(source_id);
return expanded_id;
}
@@ -1135,53 +1124,69 @@ void TextureCache::InitializeImage(ImageId id) {
}
}
void TextureCache::PrepareDccClear(ImageId id, const ImageDesc& desc) {
void TextureCache::MaterializeDccClear(ImageId id, const ImageDesc& desc,
uint32_t metadata_base_layer) {
if (desc.info.metadata.kind != ImageMetadataKind::Dcc) {
return;
}
auto& image = m_slot_images[id];
image.info.metadata = desc.info.metadata;
auto [entry, inserted] = m_surface_metas.try_emplace(
desc.info.metadata.range.address, MetaDataInfo {.type = MetaDataInfo::Type::Dcc});
auto& metadata = entry->second;
if (!inserted && metadata.type == MetaDataInfo::Type::PendingDcc) {
metadata.type = MetaDataInfo::Type::Dcc;
} else if (metadata.type != MetaDataInfo::Type::Dcc) {
EXIT("TextureCache: image reuses non-DCC metadata\n");
const auto range = desc.info.metadata.range;
{
std::scoped_lock lock {m_lock};
auto& image = m_slot_images[id];
image.info.metadata = desc.info.metadata;
// A native DCC allocation must not retain a reused HTile/CMask/FMask interpretation.
m_surface_metas.erase(range.address);
if (range.size == 0 || desc.info.resources.levels != 1 || image.info.resources.levels != 1) {
return;
}
}
if (metadata.clear_mask == 0 || image.info.resources.levels != 1 ||
desc.info.metadata.range.size == 0 || metadata.fill_size < desc.info.metadata.range.size) {
return;
}
vk::ClearValue clear {};
if (!DecodeDccClear(desc, image.backing.format, metadata.fill_value, clear.color)) {
return;
const auto layers = desc.info.TransferLayers();
// These one-mip surfaces use complete 4 KiB DCC metadata blocks.
constexpr uint64_t MetadataBlockSize = 0x1000;
if (!range.Valid() || range.address % MetadataBlockSize != 0 || layers == 0 ||
range.size % layers != 0 || (range.size / layers) % MetadataBlockSize != 0) {
EXIT("TextureCache: DCC slices must contain aligned 4 KiB blocks\n");
}
const auto& view = desc.view_info;
const bool volume_texture = image.info.IsVolume() && view.type == vk::ImageViewType::e3D;
const auto first = volume_texture ? 0u : view.base_layer;
const auto count = volume_texture ? std::max(image.info.extent.depth >> view.base_level, 1u)
: view.layer_count;
if (first >= 32 || count > 32 - first) {
return;
const bool volume_texture = desc.info.IsVolume() && view.type == vk::ImageViewType::e3D;
const auto first = volume_texture ? 0u : metadata_base_layer;
const auto image_first = volume_texture ? 0u : view.base_layer;
const auto count = volume_texture ? desc.info.extent.depth : view.layer_count;
if (first >= layers || count > layers - first) {
EXIT("TextureCache: DCC view exceeds its native metadata slices\n");
}
// The metadata fill covers the complete allocation. Consume each layer only after its
// native image contents exist; already materialized layers may have been rendered since.
for (uint32_t layer = first; layer < first + count;) {
if ((metadata.clear_mask & (1u << layer)) == 0) {
layer++;
// Finish native metadata writes before reading backing bytes. This can submit the scheduler,
// so discovery runs before final draw uploads and never holds the texture lock across it.
if (m_buffer_cache.IsRegionGpuModified(range.address, range.size)) {
m_buffer_cache.ReadMemory(range.address, range.size, false);
}
const auto slice_size = range.size / layers;
for (uint32_t slice = 0; slice < count; slice++) {
const auto address = range.address + slice_size * (first + slice);
uint8_t code = 0;
if (!LibKernel::Memory::TryReadBacking(address, &code, sizeof(code))) {
EXIT("TextureCache: failed to read DCC metadata backing\n");
}
vk::ClearValue clear {};
if (!DecodeDccClear(desc, code, clear.color)) {
continue;
}
const auto start = layer;
uint32_t mask = 0;
do {
mask |= 1u << layer++;
} while (layer < first + count && (metadata.clear_mask & (1u << layer)) != 0);
ClearImage(m_scheduler.Current(), id,
{vk::ImageAspectFlagBits::eColor, view.base_level, view.level_count, start,
layer - start},
clear);
metadata.clear_mask &= ~mask;
std::vector<uint8_t> bytes(slice_size);
if (!LibKernel::Memory::TryReadBacking(address, bytes.data(), bytes.size())) {
EXIT("TextureCache: failed to read DCC metadata slice\n");
}
if (!std::all_of(bytes.begin(), bytes.end(), [code](uint8_t byte) { return byte == code; })) {
continue;
}
{
std::scoped_lock lock {m_lock};
ClearImage(m_scheduler.Current(), id, view.format,
{vk::ImageAspectFlagBits::eColor, view.base_level, view.level_count,
image_first + slice, 1}, clear);
}
// Native expanded keys own consumption. Existing buffer tracking publishes this CPU
// write to future GPU readers; FillBuffer can fault and must run outside the texture lock.
m_buffer_cache.FillBuffer(address, slice_size, UINT32_MAX, false);
}
}
@@ -1246,6 +1251,7 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
std::scoped_lock lock {m_lock};
return GetNullImage(desc);
}
const auto metadata_base_layer = desc.view_info.base_layer;
ImageId result {};
{
@@ -1313,6 +1319,7 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
image.tick_accessed_last = m_scheduler.CurrentTick();
TouchImage(image);
}
MaterializeDccClear(result, desc, metadata_base_layer);
return result;
}
@@ -1376,7 +1383,6 @@ vk::ImageView TextureCache::FindTexture(ImageId id, const ImageDesc& desc) {
image.MarkGpuModified();
}
if (!image.info.data.Empty()) {
PrepareDccClear(id, desc);
RefreshImage(id);
}
switch (desc.type) {
@@ -1406,7 +1412,6 @@ vk::ImageView TextureCache::FindRenderTarget(ImageId id, const ImageDesc& desc)
TouchImage(image);
image.MarkGpuModified();
image.usage.render_target = true;
PrepareDccClear(id, desc);
RefreshImage(id);
CommitGpuWrite(image);
TrackImageDownload(id, image);
@@ -1430,16 +1435,9 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
RefreshImage(id);
if (desc.info.HasMetadata()) {
image.info.metadata = desc.info.metadata;
auto [metadata, inserted] =
m_surface_metas.try_emplace(desc.info.metadata.range.address,
MetaDataInfo {.type = MetaDataInfo::Type::HTile,
.clear_mask = image.info.htile_clear_mask});
if (!inserted && metadata->second.type != MetaDataInfo::Type::HTile) {
// PS5 allocations can reuse DCC storage as HTile while the old color image is cached.
// The depth binding defines the new type; incompatible fill state cannot carry over.
metadata->second = {.type = MetaDataInfo::Type::HTile,
.clear_mask = image.info.htile_clear_mask};
}
m_surface_metas.emplace(desc.info.metadata.range.address,
MetaDataInfo {.type = MetaDataInfo::Type::HTile,
.clear_mask = image.info.htile_clear_mask});
}
CommitGpuWrite(image);
if (desc.info.HasStencil()) {
@@ -1527,12 +1525,12 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address
}
clear.depthStencil.stencil = stencil_clear;
}
ClearImage(command, selected,
ClearImage(command, selected, image.backing.format,
{aspect, 0, image.info.resources.levels, 0, image.info.TransferLayers()}, clear);
return true;
}
void TextureCache::ClearImage(CommandBuffer& command, ImageId id,
void TextureCache::ClearImage(CommandBuffer& command, ImageId id, vk::Format format,
const vk::ImageSubresourceRange& range, const vk::ClearValue& clear) {
auto& image = m_slot_images[id];
const auto aspects = image.info.IsDepth() ? ImageViewOps::DepthAspectMask(image.backing.format)
@@ -1557,11 +1555,12 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id,
}
}
command.EndRendering();
if (image.info.IsVolume() && !full_image) {
// Transfer clears use the backing format; aliased clears must encode through their view.
if (format != image.backing.format || (image.info.IsVolume() && !full_image)) {
EXIT_NOT_IMPLEMENTED(range.aspectMask != vk::ImageAspectFlagBits::eColor ||
range.levelCount != 1);
ImageViewInfo view {};
view.format = image.backing.format;
view.format = format;
view.type = range.layerCount == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
view.base_level = range.baseMipLevel;
view.base_layer = range.baseArrayLayer;
@@ -1872,72 +1871,32 @@ void TextureCache::InvalidateCpuAliases(uint64_t address, uint64_t size) {
bool TextureCache::IsMeta(uint64_t address) {
std::scoped_lock lock {m_lock};
const auto found = m_surface_metas.find(address);
return found != m_surface_metas.end() && found->second.type != MetaDataInfo::Type::PendingDcc;
return found != m_surface_metas.end();
}
bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice, uint32_t* fill_value) {
bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice) {
std::scoped_lock lock {m_lock};
const auto found = m_surface_metas.find(address);
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
slice >= 32) {
if (found == m_surface_metas.end() || slice >= 32) {
return false;
}
if (fill_value != nullptr) {
*fill_value = found->second.fill_value;
}
return (found->second.clear_mask & (1u << slice)) != 0;
}
bool TextureCache::ClearMeta(uint64_t address) {
std::scoped_lock lock {m_lock};
const auto found = m_surface_metas.find(address);
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
found->second.type == MetaDataInfo::Type::Dcc) {
// Preserve the broad metadata-clear operation for CMask/FMask/HTile. DCC requires a
// validated fill value, so an arbitrary compute write must not clear it.
if (found == m_surface_metas.end()) {
return false;
}
found->second.clear_mask = UINT32_MAX;
return true;
}
void TextureCache::TrackDccFill(uint64_t address, uint64_t size, uint32_t fill_value) {
if (!GuestRange {address, size}.Valid()) {
EXIT("TextureCache: invalid DCC fill range\n");
}
// DCC fills use a repeated byte code. Require all four bytes of the detected dword to agree,
// and mark only recognized deferred-clear encodings as logically clear.
const auto dcc_clear_mask = [fill_value] {
const auto code = static_cast<uint8_t>(fill_value);
if (fill_value != static_cast<uint32_t>(code) * 0x01010101u) {
return 0u;
}
switch (code) {
case 0x00:
case 0x20:
case 0x40:
case 0x80:
case 0xc0: return UINT32_MAX;
default: return 0u;
}
}();
std::scoped_lock lock {m_lock};
// The guest dispatch still writes metadata. An unknown address remains PendingDcc until an
// image descriptor confirms its role; never reinterpret CMask/FMask/HTile as DCC.
const auto found = m_surface_metas.try_emplace(address).first;
if (found->second.type == MetaDataInfo::Type::PendingDcc ||
found->second.type == MetaDataInfo::Type::Dcc) {
found->second.clear_mask = dcc_clear_mask;
found->second.fill_value = fill_value;
found->second.fill_size = size;
}
}
bool TextureCache::TouchMeta(uint64_t address, uint32_t slice, bool is_clear) {
std::scoped_lock lock {m_lock};
const auto found = m_surface_metas.find(address);
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
slice >= 32) {
if (found == m_surface_metas.end() || slice >= 32) {
return false;
}
if (is_clear) {
@@ -1953,10 +1912,13 @@ void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
EXIT("TextureCache: invalid unmap range\n");
}
std::scoped_lock lock {m_lock};
const auto end = address + size;
for (auto metadata = m_surface_metas.lower_bound(address);
metadata != m_surface_metas.end() && metadata->first < end;) {
metadata = m_surface_metas.erase(metadata);
for (auto metadata = m_surface_metas.begin(); metadata != m_surface_metas.end();) {
const auto base = metadata->first;
if (base >= address && base < address + size) {
metadata = m_surface_metas.erase(metadata);
} else {
++metadata;
}
}
auto images = FindImagesInRegion(address, size, false);
for (const auto id: images) {
+8 -17
View File
@@ -64,11 +64,8 @@ public:
[[nodiscard]] bool IsRegionGpuModified(uint64_t address, uint64_t size);
[[nodiscard]] bool IsMeta(uint64_t address);
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice,
uint32_t* fill_value = nullptr);
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice);
[[nodiscard]] bool ClearMeta(uint64_t address);
// Record deferred DCC state while the original guest dispatch writes the metadata.
void TrackDccFill(uint64_t address, uint64_t size, uint32_t fill_value);
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
void UnmapMemory(uint64_t address, uint64_t size);
@@ -81,17 +78,10 @@ private:
struct ImageDownload;
struct MetaDataInfo {
// A guest metadata-fill dispatch may initialize DCC before its render target is bound.
// PendingDcc retains that exact fill until an image binding classifies the address,
// without exposing an unconfirmed buffer address to the normal metadata heuristics.
// Keep all surface metadata in one entry so CMask/FMask can be
// registered beside HTile and DCC without introducing parallel tracking paths.
enum class Type : uint8_t { PendingDcc, CMask, FMask, HTile, Dcc };
enum class Type : uint8_t { CMask, FMask, HTile };
Type type = Type::PendingDcc;
uint32_t clear_mask = 0;
uint32_t fill_value = 0xffffffffu;
uint64_t fill_size = 0;
Type type;
uint32_t clear_mask = UINT32_MAX;
};
struct OverlapResult {
@@ -149,7 +139,8 @@ private:
ImageId cached);
[[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId source);
void RefreshImage(ImageId id);
void PrepareDccClear(ImageId id, const ImageDesc& desc);
void MaterializeDccClear(ImageId id, const ImageDesc& desc,
uint32_t metadata_base_layer);
void InitializeImage(ImageId id);
[[nodiscard]] TextureTransfer
BuildTextureTransfer(const Image& image, BindingType binding, TransferDirection direction) const;
@@ -160,8 +151,8 @@ private:
void DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset);
void CommitGpuWrite(Image& image);
// Caller holds m_lock. Volume layer ranges select depth slices.
void ClearImage(CommandBuffer& command, ImageId id, const vk::ImageSubresourceRange& range,
const vk::ClearValue& clear);
void ClearImage(CommandBuffer& command, ImageId id, vk::Format format,
const vk::ImageSubresourceRange& range, const vk::ClearValue& clear);
void PrepareImageCopy(Image& image);
void RefreshCopySource(ImageId id);
[[nodiscard]] bool CopyD16(Image& destination, Image& source);
@@ -19,6 +19,7 @@
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <cmath>
#include <cstdarg>
@@ -45,8 +46,12 @@ static vk::StencilOp ConvertStencilOp(uint8_t value, uint8_t write_mask, uint8_t
switch (static_cast<Prospero::StencilOp>(value)) {
case Prospero::StencilOp::kKeep: return vk::StencilOp::eKeep;
case Prospero::StencilOp::kZero: return vk::StencilOp::eZero;
case Prospero::StencilOp::kReplaceTest:
case Prospero::StencilOp::kReplaceOp: return vk::StencilOp::eReplace;
case Prospero::StencilOp::kReplaceTest: return vk::StencilOp::eReplace;
case Prospero::StencilOp::kReplaceOp:
if ((op_value & write_mask) == 0) {
return vk::StencilOp::eZero;
}
return vk::StencilOp::eReplace;
case Prospero::StencilOp::kAddClamp: return vk::StencilOp::eIncrementAndClamp;
case Prospero::StencilOp::kSubClamp: return vk::StencilOp::eDecrementAndClamp;
case Prospero::StencilOp::kInvert: return vk::StencilOp::eInvert;
@@ -66,9 +71,38 @@ static vk::StencilOp ConvertStencilOp(uint8_t value, uint8_t write_mask, uint8_t
}
}
static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
constexpr auto replace_op = static_cast<uint8_t>(Prospero::StencilOp::kReplaceOp);
return fail == replace_op || pass == replace_op || depth_fail == replace_op;
static vk::StencilOpState ConvertStencilState(
uint8_t compare, const std::array<uint8_t, 3>& operations, uint8_t op_value,
const vk::StencilOpState& state) {
const auto test_value = state.reference;
auto reference = test_value;
auto required_bits = state.compareMask;
if (compare == static_cast<uint8_t>(vk::CompareOp::eAlways) ||
compare == static_cast<uint8_t>(vk::CompareOp::eNever)) {
required_bits = 0;
}
std::array<vk::StencilOp, 3> converted {};
for (size_t i = 0; i < operations.size(); i++) {
converted[i] = ConvertStencilOp(operations[i], state.writeMask, op_value);
if (converted[i] != vk::StencilOp::eReplace) {
continue;
}
auto replacement = test_value;
if (static_cast<Prospero::StencilOp>(operations[i]) == Prospero::StencilOp::kReplaceOp) {
replacement = op_value;
}
if (((reference ^ replacement) & required_bits & state.writeMask) != 0) {
DepthFatal("unsupported stencil replacement: compare=%u, compare mask=0x%02" PRIx32
", write mask=0x%02" PRIx32 ", operation value=0x%02" PRIx8
", test value=0x%02" PRIx32,
compare, state.compareMask, state.writeMask, op_value, test_value);
}
// Vulkan shares one reference between comparison and every replacement on this face.
reference = (reference & ~state.writeMask) | (replacement & state.writeMask);
required_bits |= state.writeMask;
}
return {converted[0], converted[1], converted[2], static_cast<vk::CompareOp>(compare),
state.compareMask, state.writeMask, reference};
}
[[nodiscard]] static vk::Format ResolveHostDepthAttachmentFormat(const CommandBuffer& buffer,
@@ -296,31 +330,20 @@ void RenderExecutor::ResolveRenderDepthTarget(CommandBuffer& buffer, RenderDepth
const uint8_t back_write_mask = stencil_ops_disabled ? 0 : sm.stencil_writemask_bf;
if (dc.stencilfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
(dc.backface_enable &&
dc.stencilfunc_bf > static_cast<uint8_t>(vk::CompareOp::eAlways)) ||
(front_write_mask != 0 &&
UsesStencilOpValue(sc.stencil_fail, sc.stencil_zpass, sc.stencil_zfail) &&
sm.stencil_opval != sm.stencil_testval) ||
(dc.backface_enable && back_write_mask != 0 &&
UsesStencilOpValue(sc.stencil_fail_bf, sc.stencil_zpass_bf, sc.stencil_zfail_bf) &&
sm.stencil_opval_bf != sm.stencil_testval_bf)) {
DepthFatal("unsupported stencil compare or replacement state");
dc.stencilfunc_bf > static_cast<uint8_t>(vk::CompareOp::eAlways))) {
DepthFatal("unsupported stencil compare state");
}
r.stencil_static_front = {
ConvertStencilOp(sc.stencil_fail, front_write_mask, sm.stencil_opval),
ConvertStencilOp(sc.stencil_zpass, front_write_mask, sm.stencil_opval),
ConvertStencilOp(sc.stencil_zfail, front_write_mask, sm.stencil_opval),
static_cast<vk::CompareOp>(dc.stencilfunc)};
r.stencil_dynamic_front = {sm.stencil_mask, front_write_mask, sm.stencil_testval};
r.stencil_front = ConvertStencilState(
dc.stencilfunc, {sc.stencil_fail, sc.stencil_zpass, sc.stencil_zfail},
sm.stencil_opval, {.compareMask = sm.stencil_mask, .writeMask = front_write_mask,
.reference = sm.stencil_testval});
if (dc.backface_enable) {
r.stencil_static_back = {
ConvertStencilOp(sc.stencil_fail_bf, back_write_mask, sm.stencil_opval_bf),
ConvertStencilOp(sc.stencil_zpass_bf, back_write_mask, sm.stencil_opval_bf),
ConvertStencilOp(sc.stencil_zfail_bf, back_write_mask, sm.stencil_opval_bf),
static_cast<vk::CompareOp>(dc.stencilfunc_bf)};
r.stencil_dynamic_back = {sm.stencil_mask_bf, back_write_mask, sm.stencil_testval_bf};
r.stencil_back = ConvertStencilState(
dc.stencilfunc_bf, {sc.stencil_fail_bf, sc.stencil_zpass_bf, sc.stencil_zfail_bf},
sm.stencil_opval_bf, {.compareMask = sm.stencil_mask_bf, .writeMask = back_write_mask,
.reference = sm.stencil_testval_bf});
} else {
r.stencil_static_back = r.stencil_static_front;
r.stencil_dynamic_back = r.stencil_dynamic_front;
r.stencil_back = r.stencil_front;
}
}
auto& cache = m_context.GetTextureCache();
@@ -406,14 +429,13 @@ vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
return writes;
}
const auto face_writes = [&](const PipelineStencilStaticState& state,
const PipelineStencilDynamicState& dynamic) {
if (dynamic.writeMask == 0) {
const auto face_writes = [&](const vk::StencilOpState& state) {
if (state.writeMask == 0) {
return false;
}
bool can_pass = state.compareOp != vk::CompareOp::eNever;
bool can_fail = state.compareOp != vk::CompareOp::eAlways;
if (dynamic.compareMask == 0) {
if (state.compareMask == 0) {
switch (state.compareOp) {
case vk::CompareOp::eEqual:
case vk::CompareOp::eLessOrEqual:
@@ -439,8 +461,7 @@ vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
(can_pass && depth_fail && state.depthFailOp != vk::StencilOp::eKeep);
};
if (stencil_clear_enable ||
(stencil_test_enable && (face_writes(stencil_static_front, stencil_dynamic_front) ||
face_writes(stencil_static_back, stencil_dynamic_back)))) {
(stencil_test_enable && (face_writes(stencil_front) || face_writes(stencil_back)))) {
writes |= vk::ImageAspectFlagBits::eStencil;
}
return writes;
@@ -34,10 +34,8 @@ struct RenderDepthInfo {
bool stencil_clear_enable = false;
uint8_t stencil_clear_value = 0;
bool stencil_test_enable = false;
PipelineStencilStaticState stencil_static_front;
PipelineStencilStaticState stencil_static_back;
PipelineStencilDynamicState stencil_dynamic_front;
PipelineStencilDynamicState stencil_dynamic_back;
vk::StencilOpState stencil_front;
vk::StencilOpState stencil_back;
ImageId image_id;
[[nodiscard]] vk::ImageAspectFlags AttachmentWriteAspects() const;
@@ -10,7 +10,6 @@
#include <algorithm>
#include <array>
#include <iterator>
namespace Libs::Graphics {
@@ -37,9 +36,8 @@ BlitHelper::BlitHelper(GraphicContext& graphics, CommandScheduler& scheduler)
m_graphics.device.createPipelineLayout(&layout_info, nullptr, &m_pipeline_layout),
"create BlitHelper pipeline layout");
m_vertex_shader = CreateShader(GPU_BLIT_FS_TRIANGLE_SPV, std::size(GPU_BLIT_FS_TRIANGLE_SPV));
m_fragment_shader =
CreateShader(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV, std::size(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV));
m_vertex_shader = CompileSPV(GPU_BLIT_FS_TRIANGLE_SPV, m_graphics.device);
m_fragment_shader = CompileSPV(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV, m_graphics.device);
}
BlitHelper::~BlitHelper() {
@@ -60,17 +58,6 @@ BlitHelper::~BlitHelper() {
}
}
vk::ShaderModule BlitHelper::CreateShader(const uint32_t* code, size_t words) const {
EXIT_IF(code == nullptr || words == 0);
vk::ShaderModuleCreateInfo create {};
create.codeSize = words * sizeof(uint32_t);
create.pCode = code;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&create, nullptr, &module),
"create BlitHelper shader module");
return module;
}
vk::Pipeline BlitHelper::GetPipeline(PipelineKey key) {
const auto cached = std::ranges::find(m_pipelines, key, &Pipeline::key);
if (cached != m_pipelines.end()) {
@@ -37,8 +37,7 @@ private:
vk::Pipeline handle = nullptr;
};
[[nodiscard]] vk::ShaderModule CreateShader(const uint32_t* code, size_t words) const;
[[nodiscard]] vk::Pipeline GetPipeline(PipelineKey key);
[[nodiscard]] vk::Pipeline GetPipeline(PipelineKey key);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
@@ -94,9 +94,24 @@ struct ImageInfo {
bytes_per_block == other.bytes_per_block;
}
[[nodiscard]] int32_t MipOf(const ImageInfo& container) const noexcept {
if (!IsCompatible(container) || tile_mode != container.tile_mode || resources.levels != 1 ||
container.resources.layers == 0 ||
container.resources.levels > container.mip_layout.size()) {
if (container.resources.levels > container.mip_layout.size()) {
return -1;
}
for (uint32_t level = 0; level < container.resources.levels; level++) {
if (SliceOf(container, static_cast<int32_t>(level)) >= 0) {
return static_cast<int32_t>(level);
}
}
return -1;
}
[[nodiscard]] int32_t SliceOf(const ImageInfo& container, int32_t mip) const noexcept {
if (!IsCompatible(container) || tile_mode != container.tile_mode || type != container.type ||
IsVolume() || resources.levels != 1 || resources.layers == 0 ||
container.resources.layers == 0 || mip < 0 ||
static_cast<uint32_t>(mip) >= container.resources.levels ||
container.resources.levels > container.mip_layout.size() ||
!data.Valid() || !container.data.Valid() || data.address < container.data.address ||
data.End() > container.data.End()) {
return -1;
}
if (HasStencil() != container.HasStencil() ||
@@ -105,75 +120,36 @@ struct ImageInfo {
return -1;
}
int32_t mip = -1;
for (uint32_t level = 0; level < container.resources.levels; level++) {
const auto& layout = container.mip_layout[level];
if (layout.size == 0 || layout.size % container.resources.layers != 0 ||
container.data.address > UINT64_MAX - layout.offset) {
continue;
}
const auto mip_base = container.data.address + layout.offset;
const auto slice_size = layout.size / container.resources.layers;
if (slice_size == 0 || mip_base > UINT64_MAX - layout.size) {
continue;
}
const auto mip_end = mip_base + layout.size;
if (data.address >= mip_base && data.address < mip_end &&
(data.address - mip_base) % slice_size == 0) {
mip = static_cast<int32_t>(level);
break;
}
}
if (mip < 0) {
const auto level = static_cast<uint32_t>(mip);
const auto& layout = container.mip_layout[level];
if (extent.width != std::max(container.extent.width >> level, 1u) ||
extent.height != std::max(container.extent.height >> level, 1u) ||
extent.depth != container.extent.depth || mip_layout[0].pitch != layout.pitch ||
mip_layout[0].height != layout.height || layout.size == 0 ||
layout.size % container.resources.layers != 0 ||
container.data.size % container.resources.layers != 0 ||
data.size % resources.layers != 0) {
return -1;
}
const auto level = static_cast<uint32_t>(mip);
if (extent.width != std::max(container.extent.width >> level, 1u) ||
extent.height != std::max(container.extent.height >> level, 1u)) {
// PS5 block slices contain the entire reversed mip chain.
const auto slice_stride = container.data.size / container.resources.layers;
const auto child_stride = data.size / resources.layers;
if (child_stride != layout.size / container.resources.layers ||
layout.offset >= slice_stride || child_stride > slice_stride - layout.offset ||
(resources.layers > 1 && child_stride != slice_stride)) {
return -1;
}
const auto mip_depth = std::max(container.extent.depth >> level, 1u);
if (container.type == Prospero::ImageType::kColor3D &&
type == Prospero::ImageType::kColor2D) {
if (resources.layers != mip_depth) {
return -1;
}
} else if (type != container.type) {
const auto address_delta = data.address - container.data.address;
if (address_delta < layout.offset || (address_delta - layout.offset) % slice_stride != 0) {
return -1;
}
return mip;
}
[[nodiscard]] int32_t SliceOf(const ImageInfo& container, int32_t mip) const noexcept {
if (!IsCompatible(container) || type != container.type || mip < 0 ||
static_cast<uint32_t>(mip) >= container.resources.levels ||
container.resources.levels > container.mip_layout.size() ||
container.resources.layers == 0 || data.size == 0) {
const auto layer = (address_delta - layout.offset) / slice_stride;
if (layer > INT32_MAX || layer >= container.resources.layers ||
resources.layers > container.resources.layers - layer) {
return -1;
}
const auto level = static_cast<uint32_t>(mip);
if (extent.width != std::max(container.extent.width >> level, 1u) ||
extent.height != std::max(container.extent.height >> level, 1u)) {
return -1;
}
const auto& layout = container.mip_layout[level];
if (layout.size == 0 || layout.size % container.resources.layers != 0 ||
container.data.address > UINT64_MAX - layout.offset) {
return -1;
}
const auto slice_size = layout.size / container.resources.layers;
if (slice_size == 0 || data.size % slice_size != 0) {
return -1;
}
const auto mip_base = container.data.address + layout.offset;
if (data.address < mip_base) {
return -1;
}
const auto address_delta = data.address - mip_base;
if (address_delta % data.size != 0 || address_delta / data.size > INT32_MAX) {
return -1;
}
return static_cast<int32_t>(address_delta / data.size);
return static_cast<int32_t>(layer);
}
};
+4 -18
View File
@@ -260,12 +260,8 @@ vk::Pipeline TileManager::GetPipeline(uint32_t slot) {
const uint32_t values[] {1u << element_index, direction_index};
const vk::SpecializationMapEntry entries[] {{0, 0, 4}, {1, 4, 4}};
const vk::SpecializationInfo specialization {2, entries, sizeof(values), values};
vk::ShaderModuleCreateInfo module_info {};
module_info.codeSize = shaders[family_index].words * sizeof(uint32_t);
module_info.pCode = shaders[family_index].code;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
"create TileManager shader module");
const auto module =
CompileSPV({shaders[family_index].code, shaders[family_index].words}, m_graphics.device);
vk::PipelineShaderStageCreateInfo stage {};
stage.stage = vk::ShaderStageFlagBits::eCompute;
stage.module = module;
@@ -472,12 +468,7 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
code = GPU_TILER_DEMOTE_D16_SPV;
words = std::size(GPU_TILER_DEMOTE_D16_SPV);
}
vk::ShaderModuleCreateInfo module_info {};
module_info.codeSize = words * sizeof(uint32_t);
module_info.pCode = code;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
"create D16 conversion shader module");
const auto module = CompileSPV({code, words}, m_graphics.device);
vk::PipelineShaderStageCreateInfo stage {};
stage.stage = vk::ShaderStageFlagBits::eCompute;
stage.module = module;
@@ -611,12 +602,7 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
void TileManager::SwapBgra16(Result input, Result output, uint32_t pixels) {
if (m_swap_bgra16 == nullptr) {
vk::ShaderModuleCreateInfo module_info {};
module_info.codeSize = std::size(GPU_TILER_SWAP_BGRA16_SPV) * sizeof(uint32_t);
module_info.pCode = GPU_TILER_SWAP_BGRA16_SPV;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
"create BGRA16 swap shader module");
const auto module = CompileSPV(GPU_TILER_SWAP_BGRA16_SPV, m_graphics.device);
vk::PipelineShaderStageCreateInfo stage {};
stage.stage = vk::ShaderStageFlagBits::eCompute;
stage.module = module;
@@ -15,6 +15,7 @@
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/hostMemory.h"
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
@@ -93,22 +94,15 @@ static const char* ShaderStageResourceName(ShaderType stage) {
switch (stage) {
case ShaderType::Vertex: return "Vertex";
case ShaderType::Mesh: return "Mesh";
case ShaderType::Local: return "Local";
case ShaderType::TessellationControl: return "Hull";
case ShaderType::TessellationEvaluation: return "Domain";
case ShaderType::Pixel: return "Pixel";
case ShaderType::Compute: return "Compute";
default: return "Unknown";
}
}
static vk::ShaderStageFlags NativeShaderStage(ShaderType stage) {
switch (stage) {
case ShaderType::Vertex: return vk::ShaderStageFlagBits::eVertex;
case ShaderType::Mesh: return vk::ShaderStageFlagBits::eMeshEXT;
case ShaderType::Pixel: return vk::ShaderStageFlagBits::eFragment;
case ShaderType::Compute: return vk::ShaderStageFlagBits::eCompute;
default: EXIT("unknown native shader stage\n");
}
}
static Prospero::ImageType TextureType(const ShaderTextureResource& descriptor) {
const auto type = descriptor.Type();
return type == Prospero::ImageType::kCube ? Prospero::ImageType::kColor2DArray : type;
@@ -154,7 +148,7 @@ NativeStorageBuffer(RenderContext& context, const PreparedBindings::BufferSource
}
buffer_offset = static_cast<uint32_t>(adjustment);
const vk::DescriptorBufferInfo result {buffer->Handle(), aligned_offset, size + adjustment};
if (resource.formatted && resource.written) {
if (resource.written) {
context.GetTextureCache().InvalidateMemoryFromGPU(address, size);
}
const char* access = "Read";
@@ -857,32 +851,39 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
}
}
RenderExecutor::GraphicsBindings
RenderExecutor::PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
const ShaderStageRuntime& pixel, bool pixel_active) {
GraphicsBindings bindings {
.vertex = PrepareBindings(vertex),
};
if (pixel_active) {
bindings.pixel.emplace(PrepareBindings(pixel));
void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const> stages,
std::span<RenderColorInfo> colors) {
bool uses_dma = false;
for (auto* stage: stages) {
FindBuffers(*stage);
uses_dma |= stage->runtime->program->info.uses_dma;
}
FindBuffers(bindings.vertex);
if (bindings.pixel) {
FindBuffers(*bindings.pixel);
}
if (bindings.vertex.runtime->program->info.uses_dma ||
(bindings.pixel && bindings.pixel->runtime->program->info.uses_dma)) {
if (uses_dma) {
m_context.PrepareBda();
}
RebindBuffers(bindings.vertex);
if (bindings.pixel) {
RebindBuffers(*bindings.pixel);
for (auto* stage: stages) {
RebindImages(*stage);
}
RebindImages(bindings.vertex);
if (bindings.pixel) {
RebindImages(*bindings.pixel);
auto& cache = m_context.GetTextureCache();
for (auto& target: colors) {
EXIT_IF(!target.image_id);
const auto old_image = cache.m_slot_images.try_get(target.image_id);
if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
old_image->binding.needs_rebind) {
if (old_image != nullptr) {
old_image->binding = {};
}
target.desc.view_info.base_level = target.guest_mip_level;
target.desc.view_info.base_layer = target.guest_array_layer;
target.image_id = cache.FindImage(target.desc);
BindRenderTarget(target.image_id);
}
}
// Discovery can read back PS5 metadata and submit the scheduler. Reserve draw buffers only
// after image identities are final; attachment layout transitions follow buffer alias copies.
for (auto* stage: stages) {
RebindBuffers(*stage);
}
return bindings;
}
void RenderExecutor::CommitBindings(CommandBuffer& buffer,
@@ -895,9 +896,10 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
size_t write_count = 0;
ShaderRecompiler::IR::PushData push_data;
bool has_push_data = false;
constexpr auto GraphicsStages = vk::ShaderStageFlagBits::eVertex |
vk::ShaderStageFlagBits::eMeshEXT |
vk::ShaderStageFlagBits::eFragment;
constexpr auto GraphicsStages =
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eMeshEXT |
vk::ShaderStageFlagBits::eTessellationControl |
vk::ShaderStageFlagBits::eTessellationEvaluation | vk::ShaderStageFlagBits::eFragment;
vk::ShaderStageFlags push_stages = pipeline_bind_point == vk::PipelineBindPoint::eGraphics
? vk::ShaderStageFlagBits::eFragment
: vk::ShaderStageFlags {};
@@ -236,6 +236,9 @@ struct PipelineCache::ProgramCache {
switch (options.stage) {
case ShaderType::Vertex: stage_name = "vs"; break;
case ShaderType::Mesh: stage_name = "ms"; break;
case ShaderType::Local: stage_name = "ls"; break;
case ShaderType::TessellationControl: stage_name = "hs"; break;
case ShaderType::TessellationEvaluation: stage_name = "ds"; break;
case ShaderType::Pixel: stage_name = "ps"; break;
case ShaderType::Compute: stage_name = "cs"; break;
default: EXIT("invalid pipeline shader stage\n");
@@ -250,12 +253,7 @@ struct PipelineCache::ProgramCache {
}
DumpShaderSpirv(stage_name, options.shader_hash, result.spirv);
vk::ShaderModuleCreateInfo create_info {};
create_info.codeSize = result.spirv.size() * sizeof(uint32_t);
create_info.pCode = result.spirv.data();
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(device.createShaderModule(&create_info, nullptr, &module),
"create recompiled shader module");
const auto module = CompileSPV(result.spirv, device);
EXIT_IF(module == nullptr);
if (options.dump_ir) {
LOGF("%s SPIR-V words=%" PRIu64 " wave_size=%u\n", options.dump_label,
@@ -273,7 +271,7 @@ struct PipelineCache::ProgramCache {
uint32_t& push_data_cursor) {
ShaderType stage;
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
stage = input_info.mesh.threads_num[0] != 0 ? ShaderType::Mesh : ShaderType::Vertex;
stage = input_info.logical_stage;
} else if constexpr (std::is_same_v<InputInfo, ShaderPixelInputInfo>) {
stage = ShaderType::Pixel;
} else {
@@ -325,6 +323,9 @@ struct PipelineCache::ProgramCache {
switch (stage) {
case ShaderType::Vertex: label = "ShaderRecompiler VS"; break;
case ShaderType::Mesh: label = "ShaderRecompiler MS"; break;
case ShaderType::Local: label = "ShaderRecompiler LS"; break;
case ShaderType::TessellationControl: label = "ShaderRecompiler HS"; break;
case ShaderType::TessellationEvaluation: label = "ShaderRecompiler DS"; break;
case ShaderType::Pixel: label = "ShaderRecompiler PS"; break;
case ShaderType::Compute: label = "ShaderRecompiler CS"; break;
default: EXIT("invalid pipeline shader stage\n");
@@ -341,11 +342,11 @@ struct PipelineCache::ProgramCache {
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
options.user_data_base = 8;
if (stage == ShaderType::Mesh) {
if (stage == ShaderType::Mesh || stage == ShaderType::TessellationControl) {
options.user_data_base = 0;
options.wave_size = input_info.mesh.wave_size;
options.wave_size = stage == ShaderType::Mesh ? input_info.mesh.wave_size : 64u;
}
} else if constexpr (std::is_same_v<InputInfo, ShaderComputeInputInfo>) {
} else {
options.wave_size = input_info.wave_size;
}
auto translated = ShaderRecompiler::TranslateProgram(params.code, options);
@@ -361,16 +362,19 @@ struct PipelineCache::ProgramCache {
input_info.stage = {.program = &permutation.program, .resources = std::move(resources)};
permutation.program.bindings.AdvancePushData(push_data_cursor);
std::array<size_t, static_cast<size_t>(ShaderType::Mesh) + 1> counts {};
std::array<size_t, static_cast<size_t>(ShaderType::TessellationEvaluation) + 1> counts {};
for (const auto& [key, source]: programs) {
counts[static_cast<size_t>(key.stage)] += source.permutations.size();
}
// Guest geometry shaders are compiled through the host mesh stage.
std::printf("Shaders: VS %zu | PS %zu | CS %zu | GS %zu\n",
std::printf("Shaders: VS %zu | PS %zu | CS %zu | GS %zu | LS %zu | HS %zu | TES %zu\n",
counts[static_cast<size_t>(ShaderType::Vertex)],
counts[static_cast<size_t>(ShaderType::Pixel)],
counts[static_cast<size_t>(ShaderType::Compute)],
counts[static_cast<size_t>(ShaderType::Mesh)]);
counts[static_cast<size_t>(ShaderType::Mesh)],
counts[static_cast<size_t>(ShaderType::Local)],
counts[static_cast<size_t>(ShaderType::TessellationControl)],
counts[static_cast<size_t>(ShaderType::TessellationEvaluation)]);
return permutation.handle;
}
@@ -564,12 +568,18 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
const HW::VertexShaderInfo& vertex_regs, const HW::PixelShaderInfo& pixel_regs,
const HW::ShaderRegisters& sh, const HW::Context& context, const HW::UserConfig& user_config,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping, bool pixel_active,
ShaderVertexInputInfo& vertex_info, ShaderPixelInputInfo& pixel_info) {
const auto vertex_params = PrepareProgram(vertex_regs, context, user_config, vertex_info);
const bool mesh_active = vertex_info.mesh.threads_num[0] != 0;
std::array<ShaderVertexInputInfo, 3>& vertex_info, ShaderPixelInputInfo& pixel_info) {
const bool tess_active = user_config.GetPrimType() == Prospero::PrimitiveType::kPatch;
std::array<ShaderParams, 3> vertex_params;
if (tess_active) {
vertex_params = PrepareTessellationPrograms(vertex_regs, context, vertex_info);
} else {
vertex_params[0] = PrepareProgram(vertex_regs, context, user_config, vertex_info[0]);
}
const bool mesh_active = vertex_info[0].logical_stage == ShaderType::Mesh;
if (mesh_active) {
EXIT_NOT_IMPLEMENTED(!m_graphics.mesh_shader_enabled);
auto& mesh = vertex_info.mesh;
auto& mesh = vertex_info[0].mesh;
mesh.host_subgroup_size = m_graphics.subgroup_size;
const auto& limits = m_graphics.mesh_shader_properties;
const auto logical_threads =
@@ -592,7 +602,7 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
if (context.GetClipControl().clip_disable) {
const auto& viewport = context.GetScreenViewport().viewports[0];
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
auto& clip = vertex_info.clip_space;
auto& clip = vertex_info[tess_active ? 2u : 0u].clip_space;
clip.scale[0] = viewport.xscale;
clip.scale[1] = viewport.yscale;
clip.offset[0] = viewport.xoffset;
@@ -610,7 +620,9 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
if (pixel_active) {
result.pixel = m_program_cache->Get(pixel_params, pixel_info, push_data_cursor);
}
result.vertex = m_program_cache->Get(vertex_params, vertex_info, push_data_cursor);
for (uint32_t i = 0; i < (tess_active ? 3u : 1u); i++) {
result.vertex[i] = m_program_cache->Get(vertex_params[i], vertex_info[i], push_data_cursor);
}
return result;
}
@@ -630,10 +642,12 @@ bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other)
PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
std::span<const ShaderVertexInputInfo> vertex_info, CommandBuffer& command,
const ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
bool primitive_restart_enable, const ShaderProgram& vertex_program,
const ShaderProgram& pixel_program) {
bool primitive_restart_enable, const GraphicsPrograms& programs) {
const auto& vs_input_info = vertex_info.front();
const auto& vertex_program = programs.vertex[0];
const auto& pixel_program = programs.pixel;
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Gfx)", profiler::colors::DeepOrangeA200);
EXIT_IF(colors.size() > RENDER_COLOR_ATTACHMENTS_MAX);
@@ -651,7 +665,9 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
const auto ps_id = ps_active ? pixel_program.id : 0;
GraphicsPipelineKey key {};
key.vs_shader_id = vs_id;
for (uint32_t i = 0; i < programs.vertex.size(); i++) {
key.vertex_shader_ids[i] = programs.vertex[i].id;
}
key.ps_shader_id = ps_id;
auto& static_params = key.static_params;
auto& rendering = key.rendering;
@@ -726,10 +742,8 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
static_params.depth_bounds_test_enable = depth.depth_bounds_test_enable;
static_params.depth_min_bounds = depth.depth_min_bounds;
static_params.depth_max_bounds = depth.depth_max_bounds;
static_params.stencil_test_enable = depth.stencil_test_enable;
static_params.stencil_front = depth.stencil_static_front;
static_params.stencil_back = depth.stencil_static_back;
const bool rect_list = topology == vk::PrimitiveTopology::ePatchList;
const bool rect_list =
command.GetUserConfig().GetPrimType() == Prospero::PrimitiveType::kRectList;
static_params.cull_back = !rect_list && mc.cull_back;
static_params.cull_front = !rect_list && mc.cull_front;
static_params.face = mc.face;
@@ -780,9 +794,8 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
auto cached = std::make_unique<Pipeline>();
LogPipelineTrace("CreatePipelineInternal begin", vs_id, ps_id);
CreatePipelineInternal(m_graphics, *cached, rendering, key.vertex_input, vs_input_info,
vertex_program, ps_input_info, pixel_program, static_params,
m_driver_cache);
CreatePipelineInternal(m_graphics, *cached, rendering, key.vertex_input, vertex_info,
ps_input_info, programs, static_params, m_driver_cache);
LogPipelineTrace("CreatePipelineInternal done", vs_id, ps_id);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
@@ -42,9 +42,6 @@ struct PipelineStaticParameters {
bool depth_bounds_test_enable = false;
float depth_min_bounds = 0.0f;
float depth_max_bounds = 0.0f;
bool stencil_test_enable = false;
PipelineStencilStaticState stencil_front;
PipelineStencilStaticState stencil_back;
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool cull_front = false;
bool cull_back = false;
@@ -68,7 +65,7 @@ struct PipelineStaticParameters {
static_assert(std::is_trivially_copyable_v<PipelineStaticParameters>);
static_assert(std::is_standard_layout_v<PipelineStaticParameters>);
static_assert(alignof(PipelineStaticParameters) == 1);
static_assert(sizeof(PipelineStaticParameters) == 158);
static_assert(sizeof(PipelineStaticParameters) == 125);
struct PipelineRenderingState {
std::array<vk::Format, RENDER_COLOR_ATTACHMENTS_MAX> color_formats {};
@@ -121,8 +118,10 @@ public:
};
struct GraphicsPrograms {
ShaderProgram vertex;
std::array<ShaderProgram, 3> vertex;
ShaderProgram pixel;
[[nodiscard]] uint32_t VertexStageCount() const { return vertex[1] ? 3u : 1u; }
};
GraphicsPrograms
@@ -130,18 +129,18 @@ public:
const HW::PixelShaderInfo& pixel_regs, const HW::ShaderRegisters& sh,
const HW::Context& context, const HW::UserConfig& user_config,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
bool pixel_active, ShaderVertexInputInfo& vertex_info,
bool pixel_active, std::array<ShaderVertexInputInfo, 3>& vertex_info,
ShaderPixelInputInfo& pixel_info);
ShaderProgram GetComputeProgram(const HW::ComputeShaderInfo& regs,
const HW::ShaderRegisters& sh,
ShaderComputeInputInfo& input_info);
Pipeline&
GetGraphicsPipeline(std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
const ShaderPixelInputInfo* ps_input_info,
vk::PrimitiveTopology topology, bool primitive_restart_enable,
const ShaderProgram& vertex_program, const ShaderProgram& pixel_program);
Pipeline& GetGraphicsPipeline(std::span<const RenderColorInfo> colors,
const RenderDepthInfo& depth,
std::span<const ShaderVertexInputInfo> vertex_info,
CommandBuffer& command, const ShaderPixelInputInfo* ps_input_info,
vk::PrimitiveTopology topology, bool primitive_restart_enable,
const GraphicsPrograms& programs);
Pipeline& GetComputePipeline(const ShaderComputeInputInfo& input_info,
const ShaderProgram& compute_program);
@@ -150,13 +149,13 @@ private:
struct GraphicsPipelineKey {
PipelineRenderingState rendering;
uint64_t vs_shader_id = 0;
std::array<uint64_t, 3> vertex_shader_ids {};
uint64_t ps_shader_id = 0;
PipelineVertexInputState vertex_input;
PipelineStaticParameters static_params;
bool operator==(const GraphicsPipelineKey& other) const {
return rendering == other.rendering && vs_shader_id == other.vs_shader_id &&
return rendering == other.rendering && vertex_shader_ids == other.vertex_shader_ids &&
ps_shader_id == other.ps_shader_id && vertex_input == other.vertex_input &&
static_params == other.static_params;
}
@@ -189,7 +188,9 @@ private:
std::size_t operator()(const GraphicsPipelineKey& key) const {
std::size_t hash = 0;
PipelineKeyHash::MixRendering(hash, key.rendering);
PipelineKeyHash::Mix(hash, key.vs_shader_id);
for (const auto id: key.vertex_shader_ids) {
PipelineKeyHash::Mix(hash, id);
}
PipelineKeyHash::Mix(hash, key.ps_shader_id);
PipelineKeyHash::Mix(hash, key.vertex_input.binding_count);
for (uint32_t i = 0; i < key.vertex_input.binding_count; i++) {
@@ -219,12 +220,14 @@ private:
};
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id);
void CreatePipelineInternal(
GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
const PipelineRenderingState& rendering, const PipelineVertexInputState& vertex_input,
const ShaderVertexInputInfo& vs_input_info, const ShaderProgram& vertex_program,
const ShaderPixelInputInfo* ps_input_info, const ShaderProgram& pixel_program,
const PipelineStaticParameters& static_params, vk::PipelineCache driver_cache);
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
const PipelineRenderingState& rendering,
const PipelineVertexInputState& vertex_input,
std::span<const ShaderVertexInputInfo> vertex_info,
const ShaderPixelInputInfo* ps_input_info,
const PipelineCache::GraphicsPrograms& programs,
const PipelineStaticParameters& static_params,
vk::PipelineCache driver_cache);
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
const ShaderComputeInputInfo& input_info,
vk::ShaderModule compute_module, vk::PipelineCache driver_cache);
@@ -8,6 +8,20 @@
namespace Libs::Graphics {
vk::ShaderStageFlagBits NativeShaderStage(ShaderType stage) {
switch (stage) {
case ShaderType::Local:
case ShaderType::Vertex: return vk::ShaderStageFlagBits::eVertex;
case ShaderType::Mesh: return vk::ShaderStageFlagBits::eMeshEXT;
case ShaderType::TessellationControl: return vk::ShaderStageFlagBits::eTessellationControl;
case ShaderType::TessellationEvaluation:
return vk::ShaderStageFlagBits::eTessellationEvaluation;
case ShaderType::Pixel: return vk::ShaderStageFlagBits::eFragment;
case ShaderType::Compute: return vk::ShaderStageFlagBits::eCompute;
default: EXIT("unknown native shader stage\n");
}
}
vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages) {
vk::PipelineStageFlags result = {};
if (stages & vk::ShaderStageFlagBits::eVertex) {
@@ -16,6 +30,12 @@ vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages) {
if (stages & vk::ShaderStageFlagBits::eMeshEXT) {
result |= vk::PipelineStageFlagBits::eMeshShaderEXT;
}
if (stages & vk::ShaderStageFlagBits::eTessellationControl) {
result |= vk::PipelineStageFlagBits::eTessellationControlShader;
}
if (stages & vk::ShaderStageFlagBits::eTessellationEvaluation) {
result |= vk::PipelineStageFlagBits::eTessellationEvaluationShader;
}
if (stages & vk::ShaderStageFlagBits::eFragment) {
result |= vk::PipelineStageFlagBits::eFragmentShader;
}
@@ -8,6 +8,7 @@ namespace Libs::Graphics {
struct ShaderStageRuntime;
vk::ShaderStageFlagBits NativeShaderStage(ShaderType stage);
vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages);
VulkanMemoryBarrier MakeShaderAccessDependency();
VulkanMemoryBarrier MakeShaderWriteHazardDependency();
@@ -8,6 +8,7 @@
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
@@ -205,12 +206,18 @@ static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipe
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CreatePipelineInternal(
GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
const PipelineRenderingState& rendering, const PipelineVertexInputState& vertex_input,
const ShaderVertexInputInfo& vs_input_info, const ShaderProgram& vertex_program,
const ShaderPixelInputInfo* ps_input_info, const ShaderProgram& pixel_program,
const PipelineStaticParameters& static_params, vk::PipelineCache driver_cache) {
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
const PipelineRenderingState& rendering,
const PipelineVertexInputState& vertex_input,
std::span<const ShaderVertexInputInfo> vertex_info,
const ShaderPixelInputInfo* ps_input_info,
const PipelineCache::GraphicsPrograms& programs,
const PipelineStaticParameters& static_params,
vk::PipelineCache driver_cache) {
const auto& vs_input_info = vertex_info.front();
const auto& vertex_program = programs.vertex[0];
const auto& pixel_program = programs.pixel;
const bool tessellation = vertex_info.size() == 3;
const bool ps_active = ps_input_info != nullptr;
EXIT_IF(!vertex_program || (ps_active && !pixel_program));
const bool with_depth = rendering.depth_format != vk::Format::eUndefined ||
@@ -218,46 +225,39 @@ void CreatePipelineInternal(
EXIT_IF(!vs_input_info.stage);
const bool mesh = vs_input_info.stage.program->stage == ShaderType::Mesh;
EXIT_NOT_IMPLEMENTED(mesh && !graphics.mesh_shader_enabled);
const auto vertex_stage =
mesh ? vk::ShaderStageFlagBits::eMeshEXT : vk::ShaderStageFlagBits::eVertex;
const bool rect_list = !mesh && static_params.topology == vk::PrimitiveTopology::ePatchList;
const bool rect_list =
!mesh && !tessellation && static_params.topology == vk::PrimitiveTopology::ePatchList;
vk::ShaderModule tess_control_shader_module = nullptr;
vk::ShaderModule tess_eval_shader_module = nullptr;
vk::ShaderModuleCreateInfo create_info {};
vk::Result result {};
if (rect_list) {
const auto shaders =
BuildRectListShaders(vs_input_info, ps_active ? ps_input_info : nullptr);
create_info.codeSize = shaders.control.size() * 4;
create_info.pCode = shaders.control.data();
result =
graphics.device.createShaderModule(&create_info, nullptr, &tess_control_shader_module);
tess_control_shader_module = CompileSPV(shaders.control, graphics.device);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreateShaderModule RectList TCS done result=%s module=%p\n",
vk::to_string(result).c_str(), static_cast<void*>(tess_control_shader_module));
LOGF("PipelineTrace: vkCreateShaderModule RectList TCS done module=%p\n",
static_cast<void*>(tess_control_shader_module));
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
create_info.codeSize = shaders.evaluation.size() * 4;
create_info.pCode = shaders.evaluation.data();
result =
graphics.device.createShaderModule(&create_info, nullptr, &tess_eval_shader_module);
tess_eval_shader_module = CompileSPV(shaders.evaluation, graphics.device);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreateShaderModule RectList TES done result=%s module=%p\n",
vk::to_string(result).c_str(), static_cast<void*>(tess_eval_shader_module));
LOGF("PipelineTrace: vkCreateShaderModule RectList TES done module=%p\n",
static_cast<void*>(tess_eval_shader_module));
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
}
EXIT_NOT_IMPLEMENTED(
rect_list && (tess_control_shader_module == nullptr || tess_eval_shader_module == nullptr));
vk::PipelineShaderStageCreateInfo shader_stages[4] = {
{.stage = vertex_stage, .module = vertex_program.module, .pName = "main"}};
uint32_t shader_stage_count = 1;
vk::PipelineShaderStageCreateInfo shader_stages[4] {};
uint32_t shader_stage_count = 0;
for (uint32_t i = 0; i < vertex_info.size(); i++) {
shader_stages[shader_stage_count++] = {.stage =
NativeShaderStage(vertex_info[i].logical_stage),
.module = programs.vertex[i].module,
.pName = "main"};
}
if (rect_list) {
shader_stages[shader_stage_count++] = {.stage =
vk::ShaderStageFlagBits::eTessellationControl,
@@ -432,15 +432,19 @@ void CreatePipelineInternal(
color_blending.pAttachments = color_blend_attachment;
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
AddLayoutBindings(descriptor_bindings, *vs_input_info.stage.program, vertex_stage);
vk::ShaderStageFlags graphics_stages = vk::ShaderStageFlagBits::eFragment;
for (const auto& stage: vertex_info) {
const auto native_stage = NativeShaderStage(stage.logical_stage);
AddLayoutBindings(descriptor_bindings, *stage.stage.program, native_stage);
graphics_stages |= native_stage;
}
if (ps_active) {
EXIT_IF(!ps_input_info->stage);
AddLayoutBindings(descriptor_bindings, *ps_input_info->stage.program,
vk::ShaderStageFlagBits::eFragment);
}
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
const auto GraphicsStages = vertex_stage | vk::ShaderStageFlagBits::eFragment;
const vk::PushConstantRange push_constants {GraphicsStages, 0,
const vk::PushConstantRange push_constants {graphics_stages, 0,
ShaderRecompiler::IR::NativePushConstantSize};
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
@@ -456,8 +460,8 @@ void CreatePipelineInternal(
" set_layouts=1 push_constants=%" PRIu32 "\n",
vertex_program.id, ps_active ? pixel_program.id : 0, 1u);
}
result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
&pipeline.pipeline_layout);
auto result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
&pipeline.pipeline_layout);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreatePipelineLayout done result=%s layout=%p\n",
vk::to_string(result).c_str(), static_cast<void*>(pipeline.pipeline_layout));
@@ -473,15 +477,6 @@ void CreatePipelineInternal(
#else
(static_params.depth_bounds_test_enable ? VK_TRUE : VK_FALSE);
#endif
depth_stencil_info.stencilTestEnable = (static_params.stencil_test_enable ? VK_TRUE : VK_FALSE);
depth_stencil_info.front.failOp = static_params.stencil_front.failOp;
depth_stencil_info.front.passOp = static_params.stencil_front.passOp;
depth_stencil_info.front.depthFailOp = static_params.stencil_front.depthFailOp;
depth_stencil_info.front.compareOp = static_params.stencil_front.compareOp;
depth_stencil_info.back.failOp = static_params.stencil_back.failOp;
depth_stencil_info.back.passOp = static_params.stencil_back.passOp;
depth_stencil_info.back.depthFailOp = static_params.stencil_back.depthFailOp;
depth_stencil_info.back.compareOp = static_params.stencil_back.compareOp;
depth_stencil_info.minDepthBounds = static_params.depth_min_bounds;
depth_stencil_info.maxDepthBounds = static_params.depth_max_bounds;
@@ -494,6 +489,8 @@ void CreatePipelineInternal(
vk::DynamicState::eDepthCompareOp,
vk::DynamicState::eDepthBiasEnable,
vk::DynamicState::eDepthBias,
vk::DynamicState::eStencilTestEnable,
vk::DynamicState::eStencilOp,
vk::DynamicState::eStencilCompareMask,
vk::DynamicState::eStencilReference,
vk::DynamicState::eStencilWriteMask,
@@ -524,8 +521,9 @@ void CreatePipelineInternal(
pipeline_info.pVertexInputState = mesh ? nullptr : &vertex_input_info;
pipeline_info.pInputAssemblyState = mesh ? nullptr : &input_assembly;
vk::PipelineTessellationStateCreateInfo tessellation_state {};
tessellation_state.patchControlPoints = 3;
pipeline_info.pTessellationState = (rect_list ? &tessellation_state : nullptr);
tessellation_state.patchControlPoints =
tessellation ? vs_input_info.tess.input_control_points : 3u;
pipeline_info.pTessellationState = (rect_list || tessellation) ? &tessellation_state : nullptr;
pipeline_info.pViewportState = &viewport_state;
pipeline_info.pRasterizationState = &rasterizer;
pipeline_info.pMultisampleState = &multisampling;
+3 -4
View File
@@ -170,15 +170,14 @@ private:
void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args);
struct GraphicsBindings {
PreparedBindings vertex;
std::array<PreparedBindings, 3> vertex;
std::optional<PreparedBindings> pixel;
};
[[nodiscard]] TextureBinding ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value);
[[nodiscard]] GraphicsBindings PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
const ShaderStageRuntime& pixel,
bool pixel_active);
void PrepareGraphicsBindings(std::span<PreparedBindings* const> stages,
std::span<RenderColorInfo> colors);
void ResolveRenderColorTarget(CommandBuffer& buffer, RenderColorInfo& target,
uint32_t render_target_slice_offset, uint32_t render_target_slot,
bool ignore_target_mask = false, bool exact_format = false);
@@ -172,7 +172,7 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
1, view.base_layer, view.layer_count};
vk::ClearValue clear {};
clear.depthStencil = vk::ClearDepthStencilValue {0.0f, fill.value};
cache.ClearImage(command, binding.image_id, range, clear);
cache.ClearImage(command, binding.image_id, image.backing.format, range, clear);
return true;
}
ShaderBufferResource descriptor;
@@ -183,14 +183,6 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
return false;
}
if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
// Track deferred DCC state while the original dispatch writes the metadata allocation.
cache.TrackDccFill(descriptor.Base48(), size, packed_clear);
static std::atomic<uint32_t> logged_metadata_clears {0};
if (logged_metadata_clears.fetch_add(1, std::memory_order_relaxed) < 32) {
LOGF("GraphicsRenderDispatchDirect: metadata fill shader=0x%016" PRIx64
" addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n",
input.stage.program->shader_hash, descriptor.Base48(), size, packed_clear);
}
return false;
}
static std::atomic<uint32_t> logged_clears {0};
@@ -374,8 +366,8 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
if (program.info.uses_dma) {
m_context.PrepareBda();
}
RebindBuffers(bindings);
RebindImages(bindings);
RebindBuffers(bindings);
auto vk_buffer = buffer.Handle();
PreparedBindings* descriptor_stage = &bindings;
+55 -49
View File
@@ -403,19 +403,16 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
vk_buffer.setDepthBias(constant_factor, poly_offset.clamp, slope_factor);
}
vk_buffer.setStencilTestEnable(depth.stencil_test_enable ? VK_TRUE : VK_FALSE);
if (depth.stencil_test_enable) {
vk_buffer.setStencilCompareMask(vk::StencilFaceFlagBits::eFront,
depth.stencil_dynamic_front.compareMask);
vk_buffer.setStencilCompareMask(vk::StencilFaceFlagBits::eBack,
depth.stencil_dynamic_back.compareMask);
vk_buffer.setStencilWriteMask(vk::StencilFaceFlagBits::eFront,
depth.stencil_dynamic_front.writeMask);
vk_buffer.setStencilWriteMask(vk::StencilFaceFlagBits::eBack,
depth.stencil_dynamic_back.writeMask);
vk_buffer.setStencilReference(vk::StencilFaceFlagBits::eFront,
depth.stencil_dynamic_front.reference);
vk_buffer.setStencilReference(vk::StencilFaceFlagBits::eBack,
depth.stencil_dynamic_back.reference);
const auto set_stencil = [&](vk::StencilFaceFlagBits face, const vk::StencilOpState& state) {
vk_buffer.setStencilOp(face, state.failOp, state.passOp, state.depthFailOp, state.compareOp);
vk_buffer.setStencilCompareMask(face, state.compareMask);
vk_buffer.setStencilWriteMask(face, state.writeMask);
vk_buffer.setStencilReference(face, state.reference);
};
set_stencil(vk::StencilFaceFlagBits::eFront, depth.stencil_front);
set_stencil(vk::StencilFaceFlagBits::eBack, depth.stencil_back);
}
#if defined(__APPLE__)
@@ -453,7 +450,7 @@ struct DrawRenderState {
RenderColorInfo color_info[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint32_t color_count = 0;
bool ps_active = true;
ShaderVertexInputInfo vs_input_info;
std::array<ShaderVertexInputInfo, 3> vertex_info;
ShaderPixelInputInfo ps_input_info;
PipelineCache::GraphicsPrograms programs;
};
@@ -482,14 +479,10 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
for (uint32_t i = 0; i < color_count; i++) {
auto& target = colors[i];
EXIT_IF(!target.image_id);
const auto old_image = cache.m_slot_images.try_get(target.image_id);
if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
old_image->binding.needs_rebind) {
if (old_image != nullptr) {
old_image->binding = {};
}
target.image_id = cache.FindImage(target.desc);
BindRenderTarget(target.image_id);
const auto owner = cache.m_slot_images.try_get(target.image_id);
if (owner == nullptr || (!owner->registered && !owner->info.data.Empty()) ||
owner->binding.needs_rebind) {
EXIT("color target changed after render-state discovery\n");
}
const auto image_view = cache.FindRenderTarget(target.image_id, target.desc);
auto& image = cache.GetImage(target.image_id);
@@ -709,6 +702,7 @@ struct PreparedVertexBuffers {
std::array<vk::Buffer, MaxBuffers> buffers {};
std::array<vk::DeviceSize, MaxBuffers> offsets {};
std::array<vk::DeviceSize, MaxBuffers> sizes {};
uint32_t count = 0;
};
@@ -793,6 +787,7 @@ static PreparedVertexBuffers AcquireVertexBuffers(CommandBuffer& b
prepared.buffers[i] = range->binding.first->Handle();
prepared.offsets[i] = range->binding.second + vertex.addr - range->base_address;
prepared.sizes[i] = std::min(size, range->acquired_end - vertex.addr);
SetVulkanObjectNameF(
buffer.GetContext().GetGraphics().device, prepared.buffers[i],
"Kyty.VertexBuffer[slot={} guest=0x{:016x} size=0x{:x} stride={} records={}]", i,
@@ -831,6 +826,7 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
case Prospero::PrimitiveType::kTriStrip:
topology = vk::PrimitiveTopology::eTriangleStrip;
break;
case Prospero::PrimitiveType::kPatch:
case Prospero::PrimitiveType::kRectList:
topology = vk::PrimitiveTopology::ePatchList;
break;
@@ -953,7 +949,7 @@ static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw,
}
state.programs = pipeline_cache.GetGraphicsPrograms(
vertex_shader_info, pixel_shader_info, shader_regs, ctx, buffer.GetUserConfig(),
target_export_mapping, state.ps_active, state.vs_input_info, state.ps_input_info);
target_export_mapping, state.ps_active, state.vertex_info, state.ps_input_info);
}
static PreparedIndexBuffer PrepareIndexBuffer(CommandBuffer& buffer,
@@ -988,8 +984,9 @@ static void CommitVertexBuffers(vk::CommandBuffer vk_buffer,
EXIT_IF(prepared.buffers[i] == nullptr);
}
if (prepared.count != 0) {
vk_buffer.bindVertexBuffers(0, prepared.count, prepared.buffers.data(),
prepared.offsets.data());
// Guest descriptor bounds must survive allocation merging in the cache.
vk_buffer.bindVertexBuffers2(0, prepared.count, prepared.buffers.data(),
prepared.offsets.data(), prepared.sizes.data(), nullptr);
}
}
@@ -1016,7 +1013,7 @@ static void LogDrawStateIfNeeded(const CommandBuffer& buffer, const DrawCallInfo
LogDrawTargetState(draw.Name(), state.color_info[0], state.depth_info, buffer,
state.ps_input_info, draw.index_count, 0);
}
LogDrawInputState(buffer, state.color_info[0], state.vs_input_info, index_type_and_size,
LogDrawInputState(buffer, state.color_info[0], state.vertex_info[0], index_type_and_size,
draw.index_count, index_addr);
}
@@ -1031,6 +1028,7 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
case Prospero::PrimitiveType::kTriFan:
case Prospero::PrimitiveType::kTriStrip:
case Prospero::PrimitiveType::kRectList:
case Prospero::PrimitiveType::kPatch:
if (draw.IsIndexed()) {
vk_buffer.drawIndexed(draw.index_count, draw.instance_count, 0, emit.vertex_offset,
emit.first_instance);
@@ -1069,10 +1067,12 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
const DrawIndexBufferSource& index_source,
bool primitive_restart_enable) {
auto& ucfg = buffer.GetUserConfig();
const bool mesh_active = state.vs_input_info.stage.program->stage == ShaderType::Mesh;
const auto vertex_stages =
std::span {state.vertex_info.data(), state.programs.VertexStageCount()};
const bool mesh_active = state.vertex_info[0].stage.program->stage == ShaderType::Mesh;
uint32_t mesh_groups = 0;
if (mesh_active) {
const auto& mesh = state.vs_input_info.mesh;
const auto& mesh = state.vertex_info[0].mesh;
if (primitive_restart_enable || mesh.primitives_per_group == 0) {
EXIT("unsupported mesh draw: primitive=%u indexed=%u restart=%u\n",
static_cast<uint32_t>(ucfg.GetPrimType()), draw.IsIndexed(), primitive_restart_enable);
@@ -1100,13 +1100,24 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
index_source.guest_element_size);
}
LogDrawPhase(draw.Name(), "PrepareBindings");
auto bindings = PrepareGraphicsBindings(state.vs_input_info.stage, state.ps_input_info.stage,
state.ps_active);
GraphicsBindings bindings;
std::array<PreparedBindings*, 4> descriptor_stages {};
uint32_t stage_count = 0;
for (uint32_t i = 0; i < vertex_stages.size(); i++) {
bindings.vertex[i] = PrepareBindings(state.vertex_info[i].stage);
descriptor_stages[stage_count++] = &bindings.vertex[i];
}
if (state.ps_active) {
bindings.pixel.emplace(PrepareBindings(state.ps_input_info.stage));
descriptor_stages[stage_count++] = &*bindings.pixel;
}
const auto stages = std::span {descriptor_stages.data(), stage_count};
PrepareGraphicsBindings(stages, std::span {state.color_info, state.color_count});
PreparedVertexBuffers vertex_bindings;
PreparedIndexBuffer index_binding;
if (!mesh_active) {
LogDrawPhase(draw.Name(), "PrepareVertexBuffers");
vertex_bindings = AcquireVertexBuffers(buffer, state.vs_input_info);
vertex_bindings = AcquireVertexBuffers(buffer, state.vertex_info[0]);
index_binding = PrepareIndexBuffer(buffer, index_source);
}
const auto rendering =
@@ -1117,9 +1128,9 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
LogDrawPhase(draw.Name(), "CreatePipeline");
}
auto& pipeline = m_context.GetPipelineCache().GetGraphicsPipeline(
std::span {state.color_info, state.color_count}, state.depth_info, state.vs_input_info, buffer,
std::span {state.color_info, state.color_count}, state.depth_info, vertex_stages, buffer,
state.ps_active ? &state.ps_input_info : nullptr, topology, primitive_restart_enable,
state.programs.vertex, state.programs.pixel);
state.programs);
// Resource preparation above may synchronously finish and restart the scheduler. From this
// point onward, every operation targets the current command buffer and cannot touch guest
@@ -1132,13 +1143,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
if (bindings.pixel && !draw.IsIndexed()) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
}
std::array<PreparedBindings*, 2> descriptor_stages {&bindings.vertex, nullptr};
const size_t descriptor_stage_count = bindings.pixel.has_value() ? 2u : 1u;
if (bindings.pixel) {
descriptor_stages[1] = &*bindings.pixel;
}
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline,
std::span {descriptor_stages.data(), descriptor_stage_count});
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline, stages);
if (mesh_active) {
const uint32_t draw_data[] {
draw.index_count,
@@ -1155,7 +1160,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
CommitIndexBuffer(vk_buffer, index_binding);
}
SetGraphicsDynamicParams(buffer, vk_buffer, state.vs_input_info, state.color_info,
SetGraphicsDynamicParams(buffer, vk_buffer, vertex_stages.back(), state.color_info,
state.color_count, state.depth_info);
if (m_context.GetGraphics().attachment_feedback_loop_enabled) {
vk_buffer.setAttachmentFeedbackLoopEnableEXT(
@@ -1177,16 +1182,17 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
if (mesh_active) {
vk_buffer.drawMeshTasksEXT(mesh_groups, draw.instance_count, 1);
} else {
EmitDrawPrimitives(ucfg, vk_buffer, state.vs_input_info, draw, emit);
EmitDrawPrimitives(ucfg, vk_buffer, state.vertex_info[0], draw, emit);
}
if (!draw.IsIndexed()) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x600u);
}
vk::PipelineStageFlags shader_write_stages = {};
if (HasShaderBufferWrites(state.vs_input_info.stage)) {
shader_write_stages |= mesh_active ? vk::PipelineStageFlagBits::eMeshShaderEXT
: vk::PipelineStageFlagBits::eVertexShader;
for (const auto& stage: vertex_stages) {
if (HasShaderBufferWrites(stage.stage)) {
shader_write_stages |= ShaderPipelineStages(NativeShaderStage(stage.logical_stage));
}
}
if (state.ps_active && HasShaderBufferWrites(state.ps_input_info.stage)) {
shader_write_stages |= vk::PipelineStageFlagBits::eFragmentShader;
@@ -1302,7 +1308,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs;
const auto [vertex_offset, instance_offset] =
indirect ? std::pair<int32_t, uint32_t> {0, args.first_instance}
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vs_input_info);
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
DrawEmitInfo emit {};
emit.vertex_offset = vertex_offset + args.base_vertex;
@@ -1374,9 +1380,9 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
}
RefreshShaders(buffer, draw, state);
const bool rect_list = topology == vk::PrimitiveTopology::ePatchList;
if (rect_list && state.vs_input_info.buffers_num == 0 &&
state.vs_input_info.stage.program->param_export_mask == 0 &&
const bool rect_list = ucfg.GetPrimType() == Prospero::PrimitiveType::kRectList;
if (rect_list && state.vertex_info[0].buffers_num == 0 &&
state.vertex_info[0].stage.program->param_export_mask == 0 &&
state.ps_input_info.input_num != 0) {
if (graphics_debug_dump_enabled()) {
LOGF("DrawIndexAuto: skipping rect-list draw with no VS param exports and PS inputs: "
@@ -1393,7 +1399,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs;
const auto [vertex_offset, instance_offset] =
indirect ? std::pair<int32_t, uint32_t> {0, args.first_instance}
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vs_input_info);
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
DrawEmitInfo emit {};
emit.first_vertex = static_cast<uint32_t>(vertex_offset + static_cast<int32_t>(args.first_vertex));
emit.first_instance = instance_offset;
@@ -5,7 +5,6 @@
#include <array>
#include <cstdint>
#include <type_traits>
namespace Libs::Graphics {
@@ -67,33 +66,6 @@ inline constexpr TargetViewInfo ResolveTargetViewInfo(uint32_t base_layer, uint3
base_layer, last_layer - base_layer + 1u, last_layer + 1u};
}
#pragma pack(push, 1)
struct PipelineStencilStaticState {
vk::StencilOp failOp = vk::StencilOp::eKeep;
vk::StencilOp passOp = vk::StencilOp::eKeep;
vk::StencilOp depthFailOp = vk::StencilOp::eKeep;
vk::CompareOp compareOp = vk::CompareOp::eNever;
};
struct PipelineStencilDynamicState {
uint32_t compareMask = 0;
uint32_t writeMask = 0;
uint32_t reference = 0;
};
#pragma pack(pop)
static_assert(std::is_trivially_copyable_v<PipelineStencilStaticState>);
static_assert(std::is_standard_layout_v<PipelineStencilStaticState>);
static_assert(alignof(PipelineStencilStaticState) == 1);
static_assert(sizeof(PipelineStencilStaticState) ==
sizeof(vk::StencilOp) * 3 + sizeof(vk::CompareOp));
static_assert(std::is_trivially_copyable_v<PipelineStencilDynamicState>);
static_assert(std::is_standard_layout_v<PipelineStencilDynamicState>);
static_assert(alignof(PipelineStencilDynamicState) == 1);
static_assert(sizeof(PipelineStencilDynamicState) == sizeof(uint32_t) * 3);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERTARGET_H_
+10
View File
@@ -123,4 +123,14 @@ void RequireVulkanSuccess(vk::Result result, const char* operation) {
}
}
vk::ShaderModule CompileSPV(std::span<const uint32_t> code, vk::Device device) {
vk::ShaderModuleCreateInfo create_info {};
create_info.codeSize = code.size_bytes();
create_info.pCode = code.data();
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(device.createShaderModule(&create_info, nullptr, &module),
"create SPIR-V shader module");
return module;
}
} // namespace Libs::Graphics
+2
View File
@@ -15,6 +15,7 @@
#include <cstdint>
#include <fmt/format.h>
#include <span>
#include <string>
#include <utility>
#include <vector>
@@ -27,6 +28,7 @@ using VulkanMemoryBarrier = vk::MemoryBarrier;
vk::Format VulkanFormat(Prospero::BufferFormat guest_format);
void RequireVulkanSuccess(vk::Result result, const char* operation);
vk::ShaderModule CompileSPV(std::span<const uint32_t> code, vk::Device device);
template <typename Handle, typename... Args>
void SetVulkanObjectNameF(vk::Device device, Handle handle, fmt::format_string<Args...> format,
+13 -18
View File
@@ -237,7 +237,9 @@ private:
struct VideoOutDriver::Impl {
public:
static constexpr int VIDEO_OUT_NUM_MAX = 4;
static constexpr std::array VIDEO_OUT_BUSES {
VIDEO_OUT_BUS_TYPE_MAIN, VIDEO_OUT_BUS_TYPE_OVERLAY, VIDEO_OUT_BUS_TYPE_SUB};
static constexpr int VIDEO_OUT_NUM_MAX = VIDEO_OUT_BUSES.size() + 1;
Impl(uint32_t width, uint32_t height, Graphics::Presenter& presenter)
: m_renderer(presenter.Renderer()), m_presenter(presenter), m_flip_queue(presenter) {
@@ -248,7 +250,7 @@ public:
~Impl();
KYTY_CLASS_NO_COPY(Impl);
int Open(int bus_type);
int Open(int bus_type, int index);
bool Close(int handle);
VideoOutConfig* Get(int handle);
VideoOutConfig* Get(int handle, uint64_t& generation);
@@ -610,12 +612,17 @@ void VideoOutDriver::Impl::Init(uint32_t width, uint32_t height) {
}
}
int VideoOutDriver::Impl::Open(int bus_type) {
int VideoOutDriver::Impl::Open(int bus_type, int index) {
const auto bus = std::find(VIDEO_OUT_BUSES.begin(), VIDEO_OUT_BUSES.end(), bus_type);
if (bus == VIDEO_OUT_BUSES.end()) {
return VIDEO_OUT_ERROR_INVALID_VALUE;
}
EXIT_NOT_IMPLEMENTED(index != 0);
Common::LockGuard lock(m_mutex);
const int handle = bus_type + 1;
const int handle = static_cast<int>(bus - VIDEO_OUT_BUSES.begin()) + 1;
if (m_video_out_ctx[handle].opened) {
return -1;
return VIDEO_OUT_ERROR_RESOURCE_BUSY;
}
auto& config = m_video_out_ctx[handle];
Common::LockGuard config_lock(config.mutex);
@@ -1169,21 +1176,9 @@ KYTY_SYSV_ABI int VideoOutOpen(int user_id, int bus_type, int index, const void*
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(user_id != 255 && user_id != 0);
if (bus_type != VIDEO_OUT_BUS_TYPE_MAIN && bus_type != VIDEO_OUT_BUS_TYPE_OVERLAY &&
bus_type != VIDEO_OUT_BUS_TYPE_SUB) {
return VIDEO_OUT_ERROR_INVALID_VALUE;
}
EXIT_NOT_IMPLEMENTED(index != 0);
LOGF("\t param = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(param));
int handle = DriverState().Open(bus_type);
if (handle < 0) {
return VIDEO_OUT_ERROR_RESOURCE_BUSY;
}
return handle;
return DriverState().Open(bus_type, index);
}
KYTY_SYSV_ABI int VideoOutClose(int handle) {
@@ -1,4 +1,5 @@
#include "graphics/shader/recompiler/ShaderRecompiler.h"
#include "graphics/shader/recompiler/Tessellation.h"
#include "common/assert.h"
#include "common/logging/log.h"
@@ -47,6 +48,9 @@ const char* StageName(ShaderType stage) {
switch (stage) {
case ShaderType::Compute: return "CS";
case ShaderType::Vertex: return "VS";
case ShaderType::Local: return "LS";
case ShaderType::TessellationControl: return "HS";
case ShaderType::TessellationEvaluation: return "TES";
case ShaderType::Mesh: return "MS";
case ShaderType::Pixel: return "PS";
default: return "unknown";
@@ -211,6 +215,8 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
const ShaderVertexInputInfo* input_info,
uint32_t user_data_base, uint32_t user_data_count,
uint32_t wave_size) {
const uint32_t vertex_index_reg = input_info->logical_stage == ShaderType::Local ? 2u : 5u;
const uint32_t instance_index_reg = input_info->logical_stage == ShaderType::Local ? 5u : 8u;
EmbeddedFetchData data;
data.loads.reserve(input_info->resources_num);
int32_t vertex_offset_candidate = -1;
@@ -251,16 +257,17 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
// corresponding direct-draw offsets before fetching.
const bool vertex_index_accumulator =
IsDecodedVgpr(inst.dst) &&
(inst.dst.reg == 0 || (user_data_base == 8 && inst.dst.reg == 5));
(inst.dst.reg == 0 || (user_data_base == 8 && inst.dst.reg == vertex_index_reg));
const bool instance_index_accumulator =
IsDecodedVgpr(inst.dst) && (inst.dst.reg == (user_data_base == 8 ? 8u : 3u));
IsDecodedVgpr(inst.dst) &&
(inst.dst.reg == (user_data_base == 8 ? instance_index_reg : 3u));
uint32_t sad_zero = 0;
const bool index_offset_add =
(vertex_index_accumulator || instance_index_accumulator) &&
IsDecodedSgpr(inst.src0) &&
(vertex_index_accumulator || instance_index_accumulator) && IsDecodedSgpr(inst.src0) &&
((inst.opcode == Decoder::Opcode::V_ADD_I32 && IsDecodedVgpr(inst.src1) &&
inst.src1.reg == inst.dst.reg) ||
(user_data_base == 8 && (inst.dst.reg == 5 || inst.dst.reg == 8) &&
(user_data_base == 8 &&
(inst.dst.reg == vertex_index_reg || inst.dst.reg == instance_index_reg) &&
inst.opcode == Decoder::Opcode::V_SAD_U32 && IsDecodedVgpr(inst.src2) &&
inst.src2.reg == inst.dst.reg &&
TryDecodedOperandConstant(sgprs, inst.src1, sad_zero) && sad_zero == 0));
@@ -391,8 +398,8 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
ClearEmbeddedFetchVectorLanes(&vector_lanes, inst.dst.reg);
}
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgpr_is_index.size() &&
IsDecodedVgpr(inst.src0) && inst.src0.reg == 8 &&
IsDecodedVgpr(inst.src1) && inst.src1.reg == 5) {
IsDecodedVgpr(inst.src0) && inst.src0.reg == instance_index_reg &&
IsDecodedVgpr(inst.src1) && inst.src1.reg == vertex_index_reg) {
vgpr_is_index[inst.dst.reg] = true;
}
} else if (IsEmbeddedFetchAttribPropagationAlu(inst)) {
@@ -462,22 +469,9 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
Decoder::Program DecodeFusedProgram(std::span<const uint32_t> front, std::span<const uint32_t> back,
std::vector<uint32_t>& joined_code) {
EXIT_IF(back.empty());
Decoder::Program result;
uint32_t front_words = 0;
while (front_words < front.size()) {
auto& inst = result.instructions.emplace_back();
Decoder::DecodeInstruction(front, front_words, inst);
front_words += inst.word_count;
if (inst.opcode == Decoder::Opcode::S_SETPC_B64) {
EXIT_NOT_IMPLEMENTED(inst.src0.kind != Decoder::OperandKind::Sgpr ||
inst.src0.reg != 6u);
break;
}
EXIT_NOT_IMPLEMENTED(inst.opcode == Decoder::Opcode::S_ENDPGM);
}
EXIT_IF(result.instructions.empty() ||
result.instructions.back().opcode != Decoder::Opcode::S_SETPC_B64);
joined_code.assign(front.begin(), front.begin() + front_words);
auto result = Decoder::DecodeFrontProgram(front);
const auto front_words = static_cast<uint32_t>(result.code.size());
joined_code.assign(result.code.begin(), result.code.end());
joined_code.insert(joined_code.end(), back.begin(), back.end());
// The merged-stage ABI passes the back shader in s[6:7]. Give that handoff an
// ordinary CFG edge, retaining both bodies in one register and LDS lifetime.
@@ -505,7 +499,9 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
EXIT("shader recompiler input is empty\n");
}
if (options.stage != ShaderType::Compute && options.stage != ShaderType::Vertex &&
options.stage != ShaderType::Pixel && options.stage != ShaderType::Mesh) {
options.stage != ShaderType::Pixel && options.stage != ShaderType::Mesh &&
options.stage != ShaderType::Local && options.stage != ShaderType::TessellationControl &&
options.stage != ShaderType::TessellationEvaluation) {
EXIT("shader recompiler received unsupported stage %u\n",
static_cast<unsigned>(options.stage));
}
@@ -525,6 +521,12 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
std::vector<uint32_t> joined_code;
if (!options.back_code.empty()) {
decoded = DecodeFusedProgram(code, options.back_code, joined_code);
} else if (options.stage == ShaderType::Local) {
decoded = Decoder::DecodeFrontProgram(code);
// The separately compiled hull half runs in the next Vulkan stage.
auto& handoff = decoded.instructions.back();
handoff.opcode = Decoder::Opcode::S_ENDPGM;
handoff.src_count = 0;
} else {
Decoder::DecodeProgram(code, decoded);
}
@@ -568,8 +570,8 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
}
EmbeddedFetchData embedded_fetch;
if (options.stage == ShaderType::Vertex && options.input_info.vertex != nullptr &&
options.input_info.vertex->fetch_embedded) {
if ((options.stage == ShaderType::Vertex || options.stage == ShaderType::Local) &&
options.input_info.vertex != nullptr && options.input_info.vertex->fetch_embedded) {
embedded_fetch = DetectEmbeddedVertexFetch(
decoded, options.input_info.vertex, options.user_data_base,
static_cast<uint32_t>(options.user_data.size()), options.wave_size);
@@ -608,6 +610,7 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
IR::RemoveIdentities(ir.blocks);
IR::EliminateDeadCode(ir.blocks);
}
LowerTessellationMemory(ir, options);
IR::BuildSrtPlan(ir);
IR::EliminateDeadCode(ir.blocks);
IR::TrackResources(ir);
@@ -0,0 +1,330 @@
#include "graphics/shader/recompiler/Tessellation.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "graphics/shader/recompiler/ShaderRecompiler.h"
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h"
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h"
#include <algorithm>
#include <array>
#include <unordered_map>
#include <utility>
namespace Libs::Graphics::ShaderRecompiler {
namespace {
struct TessellationAddress {
enum class Kind { Unknown, Affine, PackedControlPoint };
Kind kind = Kind::Unknown;
uint32_t coefficient = 0;
uint32_t constant = 0;
};
uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local,
uint32_t control_points, uint32_t input_stride) {
using namespace Decoder;
using Address = TessellationAddress;
using Kind = Address::Kind;
std::array<Address, IR::NumVectorRegs> registers {};
registers[local ? 3u : 1u] =
local ? Address {Kind::Affine, 1u, 0u} : Address {Kind::PackedControlPoint};
const auto constant = [](uint32_t value) { return Address {Kind::Affine, 0u, value}; };
const auto read = [&](const Operand& operand) -> Address {
if (operand.absolute || operand.negate || operand.sdwa_sext || operand.op_sel ||
operand.op_sel_hi || operand.negate_hi || operand.dpp)
return {};
Address value;
if (operand.kind == OperandKind::IntegerInlineConstant ||
operand.kind == OperandKind::LiteralConstant) {
value = constant(operand.value);
} else if (operand.kind == OperandKind::Vgpr) {
value = registers.at(operand.reg);
}
// The native packed HS ID's low byte is the patch ordinal. The logical
// interface addresses one patch, as in the frontend's v1 initialization.
if (value.kind == Kind::PackedControlPoint && operand.sdwa_sel == 0u) return constant(0u);
return operand.sdwa_sel == 6u ? value : Address {};
};
const auto affine = [](uint64_t coefficient, uint64_t offset) {
return coefficient <= UINT32_MAX && offset <= UINT32_MAX
? Address {Kind::Affine, static_cast<uint32_t>(coefficient),
static_cast<uint32_t>(offset)}
: Address {};
};
const auto add = [&](Address lhs, Address rhs) {
return lhs.kind == Kind::Affine && rhs.kind == Kind::Affine
? affine(uint64_t {lhs.coefficient} + rhs.coefficient,
uint64_t {lhs.constant} + rhs.constant)
: Address {};
};
const auto multiply = [&](Address lhs, Address rhs) {
if ((lhs.kind == Kind::Affine && lhs.coefficient == 0u && lhs.constant == 0u) ||
(rhs.kind == Kind::Affine && rhs.coefficient == 0u && rhs.constant == 0u))
return constant(0u);
if (lhs.kind != Kind::Affine || rhs.kind != Kind::Affine) return Address {};
if (rhs.coefficient != 0u) std::swap(lhs, rhs);
return rhs.coefficient == 0u ? affine(uint64_t {lhs.coefficient} * rhs.constant,
uint64_t {lhs.constant} * rhs.constant)
: Address {};
};
const auto low24 = [&](Address value) {
if (value.kind == Kind::Affine && value.coefficient == 0u)
return constant(value.constant & 0xffffffu);
return value.kind == Kind::Affine &&
uint64_t {value.coefficient} * (control_points - 1u) + value.constant <=
0xffffffu
? value
: Address {};
};
uint32_t stride = 0;
for (const auto& inst: program.instructions) {
// Stage exits preserve the active path's definitions. An internal join
// would require merging register definitions.
EXIT_NOT_IMPLEMENTED(IsDirectBranch(inst.opcode) &&
inst.branch_target != program.instructions.back().pc);
const bool local_store =
inst.opcode == Opcode::DS_WRITE_B32 || inst.opcode == Opcode::DS_WRITE2_B32;
const bool buffer_store = inst.opcode == Opcode::BUFFER_STORE_DWORD ||
inst.opcode == Opcode::BUFFER_STORE_DWORDX2 ||
inst.opcode == Opcode::BUFFER_STORE_DWORDX3 ||
inst.opcode == Opcode::BUFFER_STORE_DWORDX4;
const bool control_store =
!local && buffer_store && inst.src2.kind == OperandKind::Sgpr && inst.src2.reg == 2u;
const bool control_read =
!local && (inst.opcode == Opcode::DS_READ_B32 || inst.opcode == Opcode::DS_READ2_B32);
if ((local && local_store) || control_store || control_read) {
const auto address = read(inst.src0);
if (address.kind != Kind::Affine) {
EXIT("%s tessellation address is not affine at pc 0x%08x\n", local ? "LS" : "HS",
inst.pc);
}
if (address.coefficient != 0u) {
const auto expected = control_read ? input_stride : stride;
EXIT_NOT_IMPLEMENTED((address.coefficient & 3u) != 0u ||
(expected != 0u && expected != address.coefficient));
if (!control_read) stride = address.coefficient;
}
}
// Store vdata is a source, despite occupying the decoder's dst field.
if (local_store || buffer_store || inst.dst.kind != OperandKind::Vgpr) continue;
const auto lhs = read(inst.src0);
const auto rhs = read(inst.src1);
const auto third = read(inst.src2);
Address value;
switch (inst.opcode) {
case Opcode::V_BFE_U32:
if (lhs.kind == Kind::PackedControlPoint && rhs.kind == Kind::Affine &&
rhs.coefficient == 0u && third.kind == Kind::Affine &&
third.coefficient == 0u) {
if (rhs.constant == 8u && third.constant == 5u) value = {Kind::Affine, 1u, 0u};
if (rhs.constant == 0u && third.constant == 8u) value = constant(0u);
}
break;
case Opcode::V_MUL_U32_U24: value = multiply(low24(lhs), low24(rhs)); break;
case Opcode::V_MAD_U32_U24: value = add(multiply(low24(lhs), low24(rhs)), third); break;
case Opcode::V_LSHL_ADD_U32:
if (rhs.kind == Kind::Affine && rhs.coefficient == 0u && rhs.constant < 32u)
value = add(multiply(lhs, constant(1u << rhs.constant)), third);
break;
case Opcode::V_LSHLREV_B32:
if (lhs.kind == Kind::Affine && lhs.coefficient == 0u && lhs.constant < 32u)
value = multiply(rhs, constant(1u << lhs.constant));
break;
case Opcode::V_SUB_NC_U32:
if (lhs.kind == Kind::Affine && rhs.kind == Kind::Affine &&
lhs.coefficient >= rhs.coefficient && lhs.constant >= rhs.constant)
value = affine(lhs.coefficient - rhs.coefficient, lhs.constant - rhs.constant);
break;
default: break;
}
if (inst.dst.sdwa_sel != 6u || inst.dst.op_sel || inst.dst.omod || inst.dst.clamp ||
inst.dst.dpp)
value = {};
for (uint32_t index = 0;
index < std::max(inst.data_dwords, 1u) && inst.dst.reg + index < registers.size(); index++) {
registers[inst.dst.reg + index] = {};
}
registers.at(inst.dst.reg) = value;
}
EXIT_NOT_IMPLEMENTED(stride == 0u);
return stride;
}
const IR::Inst* TessellationBufferBase(const IR::Inst& inst) {
if (IR::BufferAccessOf(inst.GetOpcode()) == IR::BufferAccess::None || inst.NumArgs() <= 3u)
return nullptr;
const auto* base = inst.Arg(3).Resolve().TryInstruction();
return base != nullptr && base->GetOpcode() == IR::ValueOpcode::TessellationBase ? base
: nullptr;
}
IR::Value ActiveAddress(IR::Block& block, IR::Block::iterator before, IR::Value value,
IR::Value predicate, std::unordered_map<IR::Inst*, IR::Value>& resolved) {
using namespace IR;
value = value.Resolve();
auto* source = value.TryInstruction();
if (source == nullptr) return value;
if (const auto found = resolved.find(source); found != resolved.end()) return found->second;
if (source->GetOpcode() == ValueOpcode::SelectU32) {
return source->Arg(0).Resolve() == predicate.Resolve()
? ActiveAddress(block, before, source->Arg(1), predicate, resolved)
: value;
}
if (source->GetOpcode() != ValueOpcode::IAdd32 && source->GetOpcode() != ValueOpcode::ISub32)
return value;
const auto lhs = ActiveAddress(block, before, source->Arg(0), predicate, resolved);
const auto rhs = ActiveAddress(block, before, source->Arg(1), predicate, resolved);
if (lhs != source->Arg(0).Resolve() || rhs != source->Arg(1).Resolve()) {
auto copy = block.PrependNewInst(before, source->GetOpcode(), {lhs, rhs},
source->Flags<uint64_t>());
value = Value(&*copy);
}
resolved.emplace(source, value);
return value;
}
} // namespace
void AnalyzeTessellationPrograms(std::span<const uint32_t> local, std::span<const uint32_t> control,
ShaderTessellationInputInfo& info) {
const auto local_program = Decoder::DecodeFrontProgram(local);
Decoder::Program control_program;
Decoder::DecodeProgram(control, control_program);
info.ls_stride = ReflectTessellationStride(local_program, true, info.input_control_points, 0u);
info.hs_stride = ReflectTessellationStride(control_program, false, info.output_control_points,
info.ls_stride);
LOGF("Tessellation interface: input_cp=%u output_cp=%u ls_stride=%u hs_stride=%u\n",
info.input_control_points, info.output_control_points, info.ls_stride, info.hs_stride);
}
void LowerTessellationMemory(IR::Program& program, const CompileOptions& options) {
using namespace IR;
if (options.stage != ShaderType::Local && options.stage != ShaderType::TessellationControl &&
options.stage != ShaderType::TessellationEvaluation) {
return;
}
const auto& tess = options.input_info.vertex->tess;
// Ring addresses can reuse data VGPRs. Their inactive values are irrelevant to
// a store guarded by the same EXEC predicate, but must remain intact elsewhere.
for (auto* block: program.blocks) {
for (auto it = block->begin(); it != block->end(); ++it) {
if (TessellationBufferBase(*it) == nullptr) continue;
std::unordered_map<Inst*, Value> resolved;
it->SetArg(
2, ActiveAddress(*block, it, it->Arg(2), it->Arg(it->NumArgs() - 1u), resolved));
}
}
ConstantPropagationPass(program.blocks);
uint32_t reads = 0, writes = 0, factors = 0;
for (auto* block: program.blocks) {
for (auto it = block->begin(); it != block->end(); ++it) {
auto& inst = *it;
const auto shared = SharedAccessOf(inst.GetOpcode());
const auto buffer = BufferAccessOf(inst.GetOpcode());
if (shared == SharedAccess::None && buffer == BufferAccess::None) {
continue;
}
const auto& memory = program.memory_info.at(inst.Flags<MemoryFlags>().index);
bool write = false;
uint32_t components = 0;
TessellationAttribute kind;
Value address, predicate;
if (shared != SharedAccess::None && memory.kind == ResourceKind::Lds) {
write = shared == SharedAccess::Write;
EXIT_NOT_IMPLEMENTED(memory.data_bits != 32u ||
(options.stage == ShaderType::Local
? !write
: options.stage != ShaderType::TessellationControl ||
shared != SharedAccess::Read));
kind = write ? TessellationAttribute::LocalOutput
: TessellationAttribute::ControlInput;
components = SharedComponentCount(inst.GetOpcode());
address = inst.Arg(0);
predicate = inst.Arg(inst.NumArgs() - 1u);
} else if (buffer != BufferAccess::None && memory.kind == ResourceKind::Buffer) {
const auto* base = TessellationBufferBase(inst);
if (base == nullptr) {
continue;
}
EXIT_NOT_IMPLEMENTED(memory.data_bits != 32u || memory.formatted || memory.idxen ||
!memory.offen || buffer == BufferAccess::Atomic);
write = buffer == BufferAccess::Write;
components = BufferComponentCount(inst.GetOpcode());
address = inst.Arg(2).Resolve();
predicate = inst.Arg(inst.NumArgs() - 1u);
if (base->Arg(0).U32() == 1u) {
EXIT_NOT_IMPLEMENTED(!write ||
options.stage != ShaderType::TessellationControl);
kind = TessellationAttribute::Factor;
factors += components;
} else {
kind = write ? TessellationAttribute::ControlOutput
: TessellationAttribute::EvaluationInput;
EXIT_NOT_IMPLEMENTED(write
? options.stage != ShaderType::TessellationControl
: options.stage != ShaderType::TessellationEvaluation);
if (address.IsImmediate() &&
address.U32() >= tess.hs_stride * tess.output_control_points) {
EXIT_NOT_IMPLEMENTED(!write);
kind = TessellationAttribute::PatchOutput;
}
}
} else {
continue;
}
const auto emit = [&](ValueOpcode opcode, std::initializer_list<Value> args) {
return Value(&*block->PrependNewInst(it, opcode, args));
};
std::array<Value, 4> values;
for (uint32_t component = 0; component < components; component++) {
const auto offset = memory.offset + 4u * component;
const auto byte_address =
offset == 0u ? address : emit(ValueOpcode::IAdd32, {address, Value(offset)});
if (write) {
Value data = shared != SharedAccess::None ? inst.Arg(component + 1u)
: components == 1u
? inst.Arg(4)
: emit(components == 2u ? ValueOpcode::CompositeExtractU32x2
: components == 3u ? ValueOpcode::CompositeExtractU32x3
: ValueOpcode::CompositeExtractU32x4,
{inst.Arg(4), Value(component)});
emit(ValueOpcode::SetTessellationAttribute,
{Value(static_cast<uint32_t>(kind)), byte_address, data, predicate});
writes++;
} else {
values[component] =
emit(ValueOpcode::GetTessellationAttribute,
{Value(static_cast<uint32_t>(kind)), byte_address, predicate});
reads++;
}
}
if (!write) {
Value replacement = values[0];
if (components == 2u)
replacement =
emit(ValueOpcode::CompositeConstructU32x2, {values[0], values[1]});
if (components == 3u)
replacement = emit(ValueOpcode::CompositeConstructU32x3,
{values[0], values[1], values[2]});
if (components == 4u)
replacement = emit(ValueOpcode::CompositeConstructU32x4,
{values[0], values[1], values[2], values[3]});
inst.ReplaceUsesWith(replacement);
}
inst.Invalidate();
}
}
ConstantPropagationPass(program.blocks);
RemoveIdentities(program.blocks);
EliminateDeadCode(program.blocks);
LOGF("%s tessellation lowering: reads=%u writes=%u factors=%u\n",
options.stage == ShaderType::Local ? "LS"
: options.stage == ShaderType::TessellationControl ? "HS"
: "TES",
reads, writes, factors);
}
} // namespace Libs::Graphics::ShaderRecompiler
@@ -0,0 +1,21 @@
#pragma once
#include <cstdint>
#include <span>
namespace Libs::Graphics {
struct ShaderTessellationInputInfo;
}
namespace Libs::Graphics::ShaderRecompiler {
struct CompileOptions;
namespace IR {
struct Program;
}
void AnalyzeTessellationPrograms(std::span<const uint32_t> local, std::span<const uint32_t> control,
ShaderTessellationInputInfo& info);
void LowerTessellationMemory(IR::Program& program, const CompileOptions& options);
} // namespace Libs::Graphics::ShaderRecompiler
@@ -15,8 +15,8 @@ static void AppendInstructionWords(std::vector<uint32_t>& section, const uint32_
if (words_num == 0) {
return;
}
const auto opcode = words[0];
const auto word_count = static_cast<uint32_t>(words_num);
const uint32_t opcode = words[0];
const auto word_count = static_cast<uint32_t>(words_num);
section.push_back((word_count << spv::WordCountShift) | opcode);
section.insert(section.end(), words + 1, words + words_num);
}
@@ -38,7 +38,7 @@ void Builder::RequireVersion(uint32_t version) {
void Builder::RequireCapability(spv::Capability capability) {
if (m_required_capabilities.insert(capability).second) {
AppendInstruction(m_capabilities, spv::OpCapability, {capability});
AppendInstruction(m_capabilities, spv::OpCapability, capability);
}
}
@@ -62,37 +62,25 @@ uint32_t Builder::Import(const char* name) {
return id;
}
uint32_t Builder::Type(spv::Op opcode, std::initializer_list<uint32_t> operands) {
return Type(opcode, std::vector<uint32_t>(operands));
}
uint32_t Builder::Type(spv::Op opcode, const std::vector<uint32_t>& operands) {
std::vector<uint32_t> key;
key.reserve(operands.size() + 2u);
key.push_back(opcode);
key.push_back(static_cast<uint32_t>(operands.size()));
key.insert(key.end(), operands.begin(), operands.end());
uint32_t Builder::DeclareType(spv::Op opcode, std::vector<uint32_t> key) {
if (const auto it = m_declaration_ids.find(key); it != m_declaration_ids.end()) {
return it->second;
}
const auto id = AllocateId();
AppendInstruction(m_declarations, opcode, id, std::span<const uint32_t>(key).subspan(2));
m_declaration_ids.emplace(std::move(key), id);
std::vector<uint32_t> words {opcode, id};
words.insert(words.end(), operands.begin(), operands.end());
AppendInstructionWords(m_declarations, words.data(), words.size());
return id;
}
uint32_t Builder::DecoratedType(spv::Op opcode, std::initializer_list<uint32_t> operands,
std::initializer_list<TypeAnnotation> annotations) {
uint32_t Builder::DeclareDecoratedType(spv::Op opcode, std::vector<uint32_t> key,
std::initializer_list<TypeAnnotation> annotations) {
if (annotations.size() == 0) {
return Type(opcode, operands);
return DeclareType(opcode, std::move(key));
}
std::vector<uint32_t> key {opcode, static_cast<uint32_t>(operands.size())};
key.insert(key.end(), operands.begin(), operands.end());
const auto operand_count = key[1];
key.push_back(static_cast<uint32_t>(annotations.size()));
for (const auto& annotation: annotations) {
key.push_back(annotation.opcode);
AppendOperand(key, annotation.opcode);
key.push_back(static_cast<uint32_t>(annotation.operands.size()));
key.insert(key.end(), annotation.operands.begin(), annotation.operands.end());
}
@@ -100,37 +88,23 @@ uint32_t Builder::DecoratedType(spv::Op opcode, std::initializer_list<uint32_t>
return it->second;
}
const auto id = AllocateId();
AppendInstruction(m_declarations, opcode, id,
std::span<const uint32_t>(key).subspan(2, operand_count));
m_declaration_ids.emplace(std::move(key), id);
std::vector<uint32_t> words {opcode, id};
words.insert(words.end(), operands.begin(), operands.end());
AppendInstructionWords(m_declarations, words.data(), words.size());
for (const auto& annotation: annotations) {
words = {annotation.opcode, id};
words.insert(words.end(), annotation.operands.begin(), annotation.operands.end());
AppendInstructionWords(m_annotations, words.data(), words.size());
AppendInstruction(m_annotations, annotation.opcode, id, annotation.operands);
}
return id;
}
uint32_t Builder::Constant(spv::Op opcode, uint32_t type,
std::initializer_list<uint32_t> operands) {
return Constant(opcode, type, std::vector<uint32_t>(operands));
}
uint32_t Builder::Constant(spv::Op opcode, uint32_t type, const std::vector<uint32_t>& operands) {
std::vector<uint32_t> key;
key.reserve(operands.size() + 2u);
key.push_back(opcode);
key.push_back(type);
key.insert(key.end(), operands.begin(), operands.end());
uint32_t Builder::DeclareConstant(spv::Op opcode, std::vector<uint32_t> key) {
if (const auto it = m_declaration_ids.find(key); it != m_declaration_ids.end()) {
return it->second;
}
const auto id = AllocateId();
AppendInstruction(m_declarations, opcode, key[1], id,
std::span<const uint32_t>(key).subspan(2));
m_declaration_ids.emplace(std::move(key), id);
std::vector<uint32_t> words {opcode, type, id};
words.insert(words.end(), operands.begin(), operands.end());
AppendInstructionWords(m_declarations, words.data(), words.size());
return id;
}
@@ -142,7 +116,7 @@ uint32_t Builder::DefineGlobalVariable(uint32_t pointer_type, spv::StorageClass
void Builder::DefineGlobalVariable(uint32_t id, uint32_t pointer_type,
spv::StorageClass storage_class) {
AppendInstruction(m_declarations, spv::OpVariable, {pointer_type, id, storage_class});
AppendInstruction(m_declarations, spv::OpVariable, pointer_type, id, storage_class);
}
void Builder::AppendString(std::vector<uint32_t>& words, const char* text) {
@@ -161,27 +135,14 @@ void Builder::AppendString(std::vector<uint32_t>& words, const char* text) {
}
}
void Builder::AppendInstruction(std::vector<uint32_t>& section, spv::Op opcode,
const std::vector<uint32_t>& operands) {
const uint32_t word_count = static_cast<uint32_t>(operands.size() + 1u);
section.push_back((word_count << spv::WordCountShift) | opcode);
section.insert(section.end(), operands.begin(), operands.end());
}
void Builder::AppendInstruction(std::vector<uint32_t>& section, spv::Op opcode,
std::initializer_list<uint32_t> operands) {
const uint32_t word_count = static_cast<uint32_t>(operands.size() + 1u);
section.push_back((word_count << spv::WordCountShift) | opcode);
section.insert(section.end(), operands.begin(), operands.end());
}
void Builder::AddMemoryModel(std::initializer_list<uint32_t> operands) {
AppendInstruction(m_memory_model, spv::OpMemoryModel, operands);
void Builder::AddMemoryModel(spv::AddressingModel addressing_model, spv::MemoryModel memory_model) {
AppendInstruction(m_memory_model, spv::OpMemoryModel, addressing_model, memory_model);
}
void Builder::AddEntryPoint(spv::ExecutionModel execution_model, uint32_t entry_point,
const char* name, const std::vector<uint32_t>& interfaces) {
std::vector<uint32_t> operands = {execution_model, entry_point};
std::vector<uint32_t> operands;
AppendOperands(operands, execution_model, entry_point);
AppendString(operands, name);
operands.insert(operands.end(), interfaces.begin(), interfaces.end());
if (m_version >= 0x00010400u) {
@@ -199,25 +160,13 @@ void Builder::AddEntryPoint(spv::ExecutionModel execution_model, uint32_t entry_
AppendInstruction(m_entry_points, spv::OpEntryPoint, operands);
}
void Builder::AddExecutionMode(std::initializer_list<uint32_t> operands) {
AppendInstruction(m_execution_modes, spv::OpExecutionMode, operands);
}
void Builder::AddName(uint32_t target, const char* name) {
std::vector<uint32_t> operands = {target};
AppendString(operands, name);
AppendInstruction(m_debug, spv::OpName, operands);
}
void Builder::AddAnnotation(std::initializer_list<uint32_t> words) {
AppendInstructionWords(m_annotations, words.begin(), words.size());
}
void Builder::AddFunction(std::initializer_list<uint32_t> words) {
AppendInstructionWords(m_functions, words.begin(), words.size());
}
void Builder::AddFunction(const std::vector<uint32_t>& words) {
void Builder::AddFunction(std::span<const uint32_t> words) {
AppendInstructionWords(m_functions, words.data(), words.size());
}
@@ -4,17 +4,21 @@
#include "common/common.h"
#include <initializer_list>
#include <iterator>
#include <map>
#include <set>
#include <span>
#include <spirv/unified1/spirv.hpp>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
namespace Libs::Graphics::ShaderRecompiler::Spirv {
struct TypeAnnotation {
spv::Op opcode = spv::OpNop;
std::vector<uint32_t> operands;
std::vector<uint32_t> operands; // Already encoded operand words.
};
struct DeferredPhi {
@@ -32,33 +36,109 @@ public:
void RequireCapability(spv::Capability capability);
void RequireExtension(const char* name);
uint32_t Import(const char* name);
uint32_t Type(spv::Op opcode, std::initializer_list<uint32_t> operands = {});
uint32_t Type(spv::Op opcode, const std::vector<uint32_t>& operands);
uint32_t DecoratedType(spv::Op opcode, std::initializer_list<uint32_t> operands,
std::initializer_list<TypeAnnotation> annotations);
uint32_t Constant(spv::Op opcode, uint32_t type, std::initializer_list<uint32_t> operands = {});
uint32_t Constant(spv::Op opcode, uint32_t type, const std::vector<uint32_t>& operands);
template <typename... Args>
uint32_t Type(spv::Op opcode, const Args&... operands) {
return DeclareType(opcode, MakeTypeKey(opcode, operands...));
}
template <typename... Args>
uint32_t DecoratedType(spv::Op opcode, std::initializer_list<TypeAnnotation> annotations,
const Args&... operands) {
return DeclareDecoratedType(opcode, MakeTypeKey(opcode, operands...), annotations);
}
template <typename... Args>
uint32_t Constant(spv::Op opcode, uint32_t type, const Args&... operands) {
std::vector<uint32_t> key;
key.reserve(2u + (0u + ... + OperandWordCount(operands)));
AppendOperands(key, opcode, type, operands...);
return DeclareConstant(opcode, std::move(key));
}
uint32_t DefineGlobalVariable(uint32_t pointer_type, spv::StorageClass storage_class);
void DefineGlobalVariable(uint32_t id, uint32_t pointer_type, spv::StorageClass storage_class);
void AddMemoryModel(std::initializer_list<uint32_t> operands);
void AddMemoryModel(spv::AddressingModel addressing_model, spv::MemoryModel memory_model);
void AddEntryPoint(spv::ExecutionModel execution_model, uint32_t entry_point, const char* name,
const std::vector<uint32_t>& interfaces);
void AddExecutionMode(std::initializer_list<uint32_t> operands);
void AddName(uint32_t target, const char* name);
void AddAnnotation(std::initializer_list<uint32_t> words);
void AddFunction(std::initializer_list<uint32_t> words);
void AddFunction(const std::vector<uint32_t>& words);
template <typename... Args>
void AddExecutionMode(uint32_t entry_point, spv::ExecutionMode mode, const Args&... operands) {
AppendInstruction(m_execution_modes, spv::OpExecutionMode, entry_point, mode, operands...);
}
void AddName(uint32_t target, const char* name);
template <typename... Args>
void AddAnnotation(spv::Op opcode, const Args&... operands) {
AppendInstruction(m_annotations, opcode, operands...);
}
template <typename... Args>
void AddFunction(spv::Op opcode, const Args&... operands) {
AppendInstruction(m_functions, opcode, operands...);
}
void AddFunction(std::span<const uint32_t> words);
DeferredPhi AddDeferredPhi(uint32_t type, uint32_t result, size_t incoming_count);
void PatchDeferredPhi(DeferredPhi phi, size_t incoming, uint32_t value, uint32_t parent);
[[nodiscard]] std::vector<uint32_t> Build() const;
private:
static void AppendOperand(std::vector<uint32_t>& words, uint32_t value) {
words.push_back(value);
}
static void AppendOperand(std::vector<uint32_t>& words, int32_t value) {
words.push_back(static_cast<uint32_t>(value));
}
template <typename T>
requires std::is_enum_v<T>
static void AppendOperand(std::vector<uint32_t>& words, T value) {
static_assert(sizeof(T) == sizeof(uint32_t));
words.push_back(static_cast<uint32_t>(value));
}
static void AppendOperand(std::vector<uint32_t>& words, std::span<const uint32_t> values) {
words.insert(words.end(), values.begin(), values.end());
}
template <typename... Args>
static void AppendOperands(std::vector<uint32_t>& words, const Args&... operands) {
(AppendOperand(words, operands), ...);
}
template <typename T>
static size_t OperandWordCount(const T& operand) {
if constexpr (std::is_integral_v<T> || std::is_enum_v<T>) {
return 1;
} else {
return std::size(operand);
}
}
template <typename... Args>
static std::vector<uint32_t> MakeTypeKey(spv::Op opcode, const Args&... operands) {
const auto operand_count = static_cast<uint32_t>((0u + ... + OperandWordCount(operands)));
std::vector<uint32_t> key;
key.reserve(2u + operand_count);
AppendOperands(key, opcode, operand_count, operands...);
return key;
}
template <typename... Args>
static void AppendInstruction(std::vector<uint32_t>& section, spv::Op opcode,
const std::vector<uint32_t>& operands);
static void AppendInstruction(std::vector<uint32_t>& section, spv::Op opcode,
std::initializer_list<uint32_t> operands);
const Args&... operands) {
const auto offset = section.size();
AppendOperands(section, opcode, operands...);
const auto word_count = static_cast<uint32_t>(section.size() - offset);
section[offset] |= word_count << spv::WordCountShift;
}
uint32_t DeclareType(spv::Op opcode, std::vector<uint32_t> key);
uint32_t DeclareDecoratedType(spv::Op opcode, std::vector<uint32_t> key,
std::initializer_list<TypeAnnotation> annotations);
uint32_t DeclareConstant(spv::Op opcode, std::vector<uint32_t> key);
static void AppendString(std::vector<uint32_t>& words, const char* text);
uint32_t m_next_id = 1;
@@ -277,7 +277,8 @@ Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program
break;
}
case IR::ValueOpcode::LaneId:
requirements.subgroup_local_invocation_id = true;
requirements.subgroup_local_invocation_id |=
program.stage != ShaderType::TessellationControl;
break;
case IR::ValueOpcode::ImageQueryLod: requirements.compute_derivatives = true; break;
case IR::ValueOpcode::ImageGatherRaw:
@@ -306,8 +307,10 @@ std::vector<uint32_t> EmitProgram(const IR::Program& program,
using namespace Emitter;
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex &&
program.stage != ShaderType::Pixel && program.stage != ShaderType::Mesh) {
Fail(program, "binary SPIR-V emitter supports compute, vertex, and pixel shaders");
program.stage != ShaderType::Pixel && program.stage != ShaderType::Mesh &&
program.stage != ShaderType::Local && program.stage != ShaderType::TessellationControl &&
program.stage != ShaderType::TessellationEvaluation) {
Fail(program, "binary SPIR-V emitter received an unsupported shader stage");
}
if (!program.srt_plan_complete || !program.resource_tracking_complete ||
!program.shader_info_complete || !program.binding_layout_complete) {
@@ -12,14 +12,14 @@ struct Pair {
Pair ExtractPair(EmitterState& state, uint32_t value) {
Pair result {state.builder.AllocateId(), state.builder.AllocateId()};
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), result.low, value, 0});
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), result.high, value, 1});
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), result.low, value, 0);
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), result.high, value, 1);
return result;
}
uint32_t MakePair(EmitterState& state, uint32_t low, uint32_t high) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({spv::OpCompositeConstruct, TypeU64(state), result, low, high});
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU64(state), result, low, high);
return result;
}
@@ -31,7 +31,7 @@ uint32_t CompareEqual64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_va
}
uint32_t CompareOrdered64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value,
uint32_t high_compare, uint32_t low_compare) {
spv::Op high_compare, spv::Op low_compare) {
const auto lhs = ExtractPair(state, lhs_value);
const auto rhs = ExtractPair(state, rhs_value);
const auto high_equal = Binary(state, spv::OpIEqual, TypeBool(state), lhs.high, rhs.high);
@@ -51,10 +51,10 @@ uint32_t EmitMulHigh(EmitterState& state, uint32_t lhs, uint32_t rhs, bool signe
rhs_operand = Unary(state, spv::OpBitcast, TypeI32(state), rhs);
}
const auto extended = state.builder.AllocateId();
state.builder.AddFunction({signed_value ? spv::OpSMulExtended : spv::OpUMulExtended, pair_type,
extended, lhs_operand, rhs_operand});
state.builder.AddFunction(signed_value ? spv::OpSMulExtended : spv::OpUMulExtended, pair_type,
extended, lhs_operand, rhs_operand);
const auto high = state.builder.AllocateId();
state.builder.AddFunction({spv::OpCompositeExtract, operand_type, high, extended, 1});
state.builder.AddFunction(spv::OpCompositeExtract, operand_type, high, extended, 1);
return signed_value ? Unary(state, spv::OpBitcast, TypeU32(state), high) : high;
}
@@ -96,7 +96,7 @@ uint32_t EmitShift64(EmitterState& state, spv::Op opcode, uint32_t value, uint32
Select(state, TypeU32(state), at_least_32, fill, high));
}
uint32_t EmitConstantShift64(EmitterState& state, uint32_t opcode, uint32_t value, uint32_t shift) {
uint32_t EmitConstantShift64(EmitterState& state, spv::Op opcode, uint32_t value, uint32_t shift) {
shift &= 63u;
if (shift == 0u) {
return value;
@@ -173,10 +173,10 @@ uint32_t EmitF32ToU32(EmitterState& state, uint32_t src, bool signed_value) {
const auto converted_raw = state.builder.AllocateId();
if (signed_value) {
const auto converted_i = state.builder.AllocateId();
state.builder.AddFunction({spv::OpConvertFToS, TypeI32(state), converted_i, trunc});
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), converted_raw, converted_i});
state.builder.AddFunction(spv::OpConvertFToS, TypeI32(state), converted_i, trunc);
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), converted_raw, converted_i);
} else {
state.builder.AddFunction({spv::OpConvertFToU, TypeU32(state), converted_raw, trunc});
state.builder.AddFunction(spv::OpConvertFToU, TypeU32(state), converted_raw, trunc);
}
const auto nan = EmitClassifyF32(state, src).nan;
if (signed_value) {
@@ -218,10 +218,10 @@ uint32_t EmitFindUMsb64(EmitterState& state, uint32_t value) {
const auto pair = ExtractPair(state, value);
const auto high_i = state.builder.AllocateId();
const auto low_i = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpExtInst, TypeI32(state), high_i, GlslStd450(state), GLSLstd450FindUMsb, pair.high});
state.builder.AddFunction(
{spv::OpExtInst, TypeI32(state), low_i, GlslStd450(state), GLSLstd450FindUMsb, pair.low});
state.builder.AddFunction(spv::OpExtInst, TypeI32(state), high_i, GlslStd450(state),
GLSLstd450FindUMsb, pair.high);
state.builder.AddFunction(spv::OpExtInst, TypeI32(state), low_i, GlslStd450(state),
GLSLstd450FindUMsb, pair.low);
const auto high = Unary(state, spv::OpBitcast, TypeU32(state), high_i);
const auto low = Unary(state, spv::OpBitcast, TypeU32(state), low_i);
const auto high_nonzero =
@@ -260,9 +260,9 @@ uint32_t EmitIAdd64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value)
const auto low_pair = state.builder.AllocateId();
const auto low = state.builder.AllocateId();
const auto carry = state.builder.AllocateId();
state.builder.AddFunction({spv::OpIAddCarry, TypeU32Pair(state), low_pair, lhs.low, rhs.low});
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), low, low_pair, 0});
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), carry, low_pair, 1});
state.builder.AddFunction(spv::OpIAddCarry, TypeU32Pair(state), low_pair, lhs.low, rhs.low);
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), low, low_pair, 0);
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), carry, low_pair, 1);
const auto high0 = Binary(state, spv::OpIAdd, TypeU32(state), lhs.high, rhs.high);
const auto high = Binary(state, spv::OpIAdd, TypeU32(state), high0, carry);
return MakePair(state, low, high);
@@ -278,8 +278,8 @@ uint32_t EmitConvertU8U32(EmitterState& state, uint32_t arg0) {
uint32_t EmitConvertF16F32(EmitterState& state, uint32_t arg0) {
const auto pair = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpCompositeConstruct, TypeF32Vector(state, 2), pair, arg0, ConstantF32(state, 0)});
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 2), pair, arg0,
ConstantF32(state, 0));
return EmitPackHalf2x16(state, pair);
}
@@ -4,35 +4,35 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
uint32_t EmitAndConstant(EmitterState& state, uint32_t value, uint32_t mask) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), ret, value, ConstantU32(state, mask)});
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), ret, value,
ConstantU32(state, mask));
return ret;
}
uint32_t EmitShiftRightConstant(EmitterState& state, uint32_t value, uint32_t shift) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpShiftRightLogical, TypeU32(state), ret, value, ConstantU32(state, shift)});
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), ret, value,
ConstantU32(state, shift));
return ret;
}
uint32_t EmitCompareU32Constant(EmitterState& state, uint32_t opcode, uint32_t value,
uint32_t EmitCompareU32Constant(EmitterState& state, spv::Op opcode, uint32_t value,
uint32_t constant) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({opcode, TypeBool(state), ret, value, ConstantU32(state, constant)});
state.builder.AddFunction(opcode, TypeBool(state), ret, value, ConstantU32(state, constant));
return ret;
}
uint32_t EmitSubConstantMinusU32(EmitterState& state, uint32_t constant, uint32_t value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpISub, TypeU32(state), ret, ConstantU32(state, constant), value});
state.builder.AddFunction(spv::OpISub, TypeU32(state), ret, ConstantU32(state, constant),
value);
return ret;
}
uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t f32) {
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, f32});
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, f32);
const auto sign = EmitAndConstant(state, EmitShiftRightConstant(state, bits, 16), 0x8000u);
const auto exp = EmitAndConstant(state, EmitShiftRightConstant(state, bits, 23), 0xffu);
@@ -43,13 +43,12 @@ uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t f32) {
const auto normal_mant = EmitShiftRightConstant(state, mant, 13);
const auto normal_payload = state.builder.AllocateId();
const auto normal = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpISub, TypeU32(state), half_exp, exp, ConstantU32(state, 112)});
state.builder.AddFunction(
{spv::OpShiftLeftLogical, TypeU32(state), normal_exp, half_exp, ConstantU32(state, 10)});
state.builder.AddFunction(
{spv::OpBitwiseOr, TypeU32(state), normal_payload, normal_exp, normal_mant});
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), normal, sign, normal_payload});
state.builder.AddFunction(spv::OpISub, TypeU32(state), half_exp, exp, ConstantU32(state, 112));
state.builder.AddFunction(spv::OpShiftLeftLogical, TypeU32(state), normal_exp, half_exp,
ConstantU32(state, 10));
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), normal_payload, normal_exp,
normal_mant);
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), normal, sign, normal_payload);
const auto mant_with_hidden = EmitOrU32(state, mant, ConstantU32(state, 0x00800000u));
const auto raw_sub_shift = EmitSubConstantMinusU32(state, 126, exp);
@@ -61,9 +60,9 @@ uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t f32) {
EmitSelectValueU32(state, exp_gt_112, ConstantU32(state, 14), sub_shift_low);
const auto sub_mant = state.builder.AllocateId();
const auto subnormal = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpShiftRightLogical, TypeU32(state), sub_mant, mant_with_hidden, sub_shift});
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), subnormal, sign, sub_mant});
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), sub_mant, mant_with_hidden,
sub_shift);
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), subnormal, sign, sub_mant);
const auto nan_payload =
EmitOrU32(state, EmitShiftRightConstant(state, mant, 13), ConstantU32(state, 0x0200u));
@@ -86,18 +85,18 @@ uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t f32) {
uint32_t EmitMinMaxU32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) {
const auto cond = state.builder.AllocateId();
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(
{max_value ? spv::OpUGreaterThan : spv::OpULessThan, TypeBool(state), cond, lhs, rhs});
state.builder.AddFunction({spv::OpSelect, TypeU32(state), ret, cond, lhs, rhs});
state.builder.AddFunction(max_value ? spv::OpUGreaterThan : spv::OpULessThan, TypeBool(state),
cond, lhs, rhs);
state.builder.AddFunction(spv::OpSelect, TypeU32(state), ret, cond, lhs, rhs);
return ret;
}
uint32_t EmitMinMaxI32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) {
const auto cond = state.builder.AllocateId();
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(
{max_value ? spv::OpSGreaterThan : spv::OpSLessThan, TypeBool(state), cond, lhs, rhs});
state.builder.AddFunction({spv::OpSelect, TypeU32(state), ret, cond, lhs, rhs});
state.builder.AddFunction(max_value ? spv::OpSGreaterThan : spv::OpSLessThan, TypeBool(state),
cond, lhs, rhs);
state.builder.AddFunction(spv::OpSelect, TypeU32(state), ret, cond, lhs, rhs);
return ret;
}
@@ -184,8 +183,8 @@ uint32_t EmitMinMaxF32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, boo
const auto rhs_class = EmitClassifyF32(state, rhs);
const auto numeric_cond = state.builder.AllocateId();
state.builder.AddFunction({max_value ? spv::OpFOrdGreaterThanEqual : spv::OpFOrdLessThan,
TypeBool(state), numeric_cond, lhs, rhs});
state.builder.AddFunction(max_value ? spv::OpFOrdGreaterThanEqual : spv::OpFOrdLessThan,
TypeBool(state), numeric_cond, lhs, rhs);
const auto ordered_bits =
EmitSelectValueU32(state, numeric_cond, lhs_class.bits, rhs_class.bits);
@@ -209,18 +208,18 @@ uint32_t EmitFlushF32DenormToSignedZero(EmitterState& state, uint32_t value) {
const auto flush = state.builder.AllocateId();
const auto selected = state.builder.AllocateId();
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, value});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), abs_bits, bits, ConstantU32(state, 0x7fffffffu)});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), sign_bits, bits, ConstantU32(state, 0x80000000u)});
state.builder.AddFunction(
{spv::OpINotEqual, TypeBool(state), non_zero, abs_bits, ConstantU32(state, 0)});
state.builder.AddFunction(
{spv::OpULessThan, TypeBool(state), subnormal, abs_bits, ConstantU32(state, 0x00800000u)});
state.builder.AddFunction({spv::OpLogicalAnd, TypeBool(state), flush, non_zero, subnormal});
state.builder.AddFunction({spv::OpSelect, TypeU32(state), selected, flush, sign_bits, bits});
state.builder.AddFunction({spv::OpBitcast, TypeF32(state), ret, selected});
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), abs_bits, bits,
ConstantU32(state, 0x7fffffffu));
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), sign_bits, bits,
ConstantU32(state, 0x80000000u));
state.builder.AddFunction(spv::OpINotEqual, TypeBool(state), non_zero, abs_bits,
ConstantU32(state, 0));
state.builder.AddFunction(spv::OpULessThan, TypeBool(state), subnormal, abs_bits,
ConstantU32(state, 0x00800000u));
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), flush, non_zero, subnormal);
state.builder.AddFunction(spv::OpSelect, TypeU32(state), selected, flush, sign_bits, bits);
state.builder.AddFunction(spv::OpBitcast, TypeF32(state), ret, selected);
return ret;
}
@@ -232,35 +231,35 @@ uint32_t EmitTrigCycleF32(EmitterState& state, uint32_t src, bool preserve_signe
const auto finite = state.builder.AllocateId();
const auto large_finite = state.builder.AllocateId();
const auto reduced = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpExtInst, TypeF32(state), fract, GlslStd450(state), GLSLstd450Fract, src});
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, src});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), abs_bits, bits, ConstantU32(state, 0x7fffffffu)});
state.builder.AddFunction({spv::OpUGreaterThanEqual, TypeBool(state), large, abs_bits,
ConstantU32(state, 0x4b000000u)});
state.builder.AddFunction(
{spv::OpULessThan, TypeBool(state), finite, abs_bits, ConstantU32(state, 0x7f800000u)});
state.builder.AddFunction({spv::OpLogicalAnd, TypeBool(state), large_finite, large, finite});
state.builder.AddFunction(
{spv::OpSelect, TypeF32(state), reduced, large_finite, ConstantF32(state, 0), fract});
state.builder.AddFunction(spv::OpExtInst, TypeF32(state), fract, GlslStd450(state),
GLSLstd450Fract, src);
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, src);
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), abs_bits, bits,
ConstantU32(state, 0x7fffffffu));
state.builder.AddFunction(spv::OpUGreaterThanEqual, TypeBool(state), large, abs_bits,
ConstantU32(state, 0x4b000000u));
state.builder.AddFunction(spv::OpULessThan, TypeBool(state), finite, abs_bits,
ConstantU32(state, 0x7f800000u));
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), large_finite, large, finite);
state.builder.AddFunction(spv::OpSelect, TypeF32(state), reduced, large_finite,
ConstantF32(state, 0), fract);
if (!preserve_signed_zero) {
return reduced;
}
const auto zero = state.builder.AllocateId();
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpIEqual, TypeBool(state), zero, abs_bits, ConstantU32(state, 0)});
state.builder.AddFunction({spv::OpSelect, TypeF32(state), ret, zero, src, reduced});
state.builder.AddFunction(spv::OpIEqual, TypeBool(state), zero, abs_bits,
ConstantU32(state, 0));
state.builder.AddFunction(spv::OpSelect, TypeF32(state), ret, zero, src, reduced);
return ret;
}
uint32_t EmitF16BitsToF32(EmitterState& state, uint32_t bits) {
const auto unpacked = state.builder.AllocateId();
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({spv::OpExtInst, TypeF32Vector(state, 2), unpacked, GlslStd450(state),
GLSLstd450UnpackHalf2x16, bits});
state.builder.AddFunction({spv::OpCompositeExtract, TypeF32(state), ret, unpacked, 0});
state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 2), unpacked, GlslStd450(state),
GLSLstd450UnpackHalf2x16, bits);
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), ret, unpacked, 0);
return ret;
}
@@ -82,8 +82,8 @@ uint32_t DescriptorElementPointer(EmitterState& state, uint32_t result_ptr_type,
ExitDescriptorBindingFailure(state, kind, resource, variable_name);
}
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain, result_ptr_type, pointer, variable_id,
ConstantU32(state, array_index)});
state.builder.AddFunction(spv::OpAccessChain, result_ptr_type, pointer, variable_id,
ConstantU32(state, array_index));
return pointer;
}
@@ -109,8 +109,7 @@ uint32_t ImageScalarType(EmitterState& state, Prospero::TextureNumericClass nume
uint32_t ImageVectorType(EmitterState& state, Prospero::TextureNumericClass numeric_class,
uint32_t components) {
return state.builder.Type(spv::OpTypeVector,
{ImageScalarType(state, numeric_class), components});
return state.builder.Type(spv::OpTypeVector, ImageScalarType(state, numeric_class), components);
}
uint32_t ImageType(EmitterState& state, const IR::ImageResource& image) {
@@ -131,10 +130,9 @@ uint32_t ImageType(EmitterState& state, const IR::ImageResource& image) {
EXIT("invalid image resource class");
}
const auto& info = ImageDimensionInfoFor(image.dimension);
return state.builder.Type(spv::OpTypeImage,
{ImageScalarType(state, image.numeric_class), info.spirv_dimension,
image.depth_compare ? 1u : 0u, info.arrayed, info.multisampled,
sampled, format});
return state.builder.Type(spv::OpTypeImage, ImageScalarType(state, image.numeric_class),
info.spirv_dimension, image.depth_compare ? 1u : 0u, info.arrayed,
info.multisampled, sampled, format);
}
uint32_t ImageViewSizeType(EmitterState& state, ImageDimension dimension) {
@@ -154,12 +152,12 @@ uint32_t LoadSampledImageDescriptor(EmitterState& state, uint32_t resource) {
const auto array_index = ResourceForDescriptor(state, *kind, resource);
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
const auto pointer_type = state.builder.Type(
spv::OpTypePointer, {spv::StorageClassUniformConstant, ImageType(state, image_resource)});
spv::OpTypePointer, spv::StorageClassUniformConstant, ImageType(state, image_resource));
const auto pointer =
DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource,
"sampled image descriptor array was not emitted");
const auto image = state.builder.AllocateId();
state.builder.AddFunction({spv::OpLoad, ImageType(state, image_resource), image, pointer});
state.builder.AddFunction(spv::OpLoad, ImageType(state, image_resource), image, pointer);
return image;
}
@@ -168,12 +166,12 @@ uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler) {
ResourceForDescriptor(state, IR::DescriptorBindingKind::Samplers, sampler);
const auto sampler_type = state.builder.Type(spv::OpTypeSampler);
const auto pointer_type =
state.builder.Type(spv::OpTypePointer, {spv::StorageClassUniformConstant, sampler_type});
state.builder.Type(spv::OpTypePointer, spv::StorageClassUniformConstant, sampler_type);
const auto pointer = DescriptorElementPointer(
state, pointer_type, state.sampler_variable, array_index,
IR::DescriptorBindingKind::Samplers, sampler, "sampler descriptor array was not emitted");
const auto sampler_id = state.builder.AllocateId();
state.builder.AddFunction({spv::OpLoad, sampler_type, sampler_id, pointer});
state.builder.AddFunction(spv::OpLoad, sampler_type, sampler_id, pointer);
return sampler_id;
}
@@ -183,9 +181,8 @@ uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampl
const auto sampler_id = LoadSamplerDescriptor(state, sampler);
const auto sampled_image = state.builder.AllocateId();
const auto sampled_type =
state.builder.Type(spv::OpTypeSampledImage, {ImageType(state, image_resource)});
state.builder.AddFunction(
{spv::OpSampledImage, sampled_type, sampled_image, image, sampler_id});
state.builder.Type(spv::OpTypeSampledImage, ImageType(state, image_resource));
state.builder.AddFunction(spv::OpSampledImage, sampled_type, sampled_image, image, sampler_id);
return sampled_image;
}
@@ -194,9 +191,9 @@ uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource) {
EXIT_IF(image.resource_class != IR::ImageResourceClass::Storage);
const auto kind = IR::DescriptorBindingForImage(image);
EXIT_IF(!kind.has_value());
const auto array_index = ResourceForDescriptor(state, *kind, resource);
const auto array_index = ResourceForDescriptor(state, *kind, resource);
const auto pointer_type = state.builder.Type(
spv::OpTypePointer, {spv::StorageClassUniformConstant, ImageType(state, image)});
spv::OpTypePointer, spv::StorageClassUniformConstant, ImageType(state, image));
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
return DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource,
"storage image descriptor array was not emitted");
@@ -214,18 +211,18 @@ void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_
const auto array_index = ResourceForDescriptor(state, *kind, resource);
const auto image_type = ImageType(state, image);
const auto pointer_type =
state.builder.Type(spv::OpTypePointer, {spv::StorageClassUniformConstant, image_type});
state.builder.Type(spv::OpTypePointer, spv::StorageClassUniformConstant, image_type);
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
const auto LoadAt = [&](uint32_t array_index) {
const auto pointer =
DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource,
"storage image descriptor array was not emitted");
const auto descriptor = state.builder.AllocateId();
state.builder.AddFunction({spv::OpLoad, image_type, descriptor, pointer});
state.builder.AddFunction(spv::OpLoad, image_type, descriptor, pointer);
return descriptor;
};
if (image.mip_mode != IR::ImageMipMode::DynamicStorage) {
state.builder.AddFunction({spv::OpImageWrite, LoadAt(array_index), coord, texel});
state.builder.AddFunction(spv::OpImageWrite, LoadAt(array_index), coord, texel);
return;
}
if (image.mip_count == 0u) {
@@ -241,19 +238,22 @@ void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_
words.push_back(mip);
words.push_back(labels[mip]);
}
state.builder.AddFunction({spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone});
state.builder.AddFunction(spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
state.builder.AddFunction(words);
for (uint32_t mip = 0; mip < image.mip_count; mip++) {
EmitLabel(state, labels[mip]);
state.builder.AddFunction({spv::OpImageWrite, LoadAt(array_index + mip), coord, texel});
state.builder.AddFunction({spv::OpBranch, merge_label});
state.builder.AddFunction(spv::OpImageWrite, LoadAt(array_index + mip), coord, texel);
state.builder.AddFunction(spv::OpBranch, merge_label);
}
EmitLabel(state, merge_label);
}
spv::ExecutionModel ExecutionModelForStage(ShaderType stage) {
switch (stage) {
case ShaderType::Local:
case ShaderType::Vertex: return spv::ExecutionModelVertex;
case ShaderType::TessellationControl: return spv::ExecutionModelTessellationControl;
case ShaderType::TessellationEvaluation: return spv::ExecutionModelTessellationEvaluation;
case ShaderType::Mesh: return spv::ExecutionModelMeshEXT; // MeshEXT
case ShaderType::Pixel: return spv::ExecutionModelFragment;
default: return spv::ExecutionModelGLCompute;
@@ -45,28 +45,30 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
if (kind == IR::StageInputKind::FrontFacing) {
const auto value = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({spv::OpLoad, TypeBool(state), value, variable});
state.builder.AddFunction({spv::OpSelect, TypeU32(state), bits, value,
ConstantU32(state, 1), ConstantU32(state, 0)});
state.builder.AddFunction(spv::OpLoad, TypeBool(state), value, variable);
// PS5 initializes v_front_face with float +1.0/-1.0 bits.
state.builder.AddFunction(spv::OpSelect, TypeU32(state), bits, value,
ConstantU32(state, 0x3f800000u), ConstantU32(state, 0xbf800000u));
return bits;
}
if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex ||
kind == IR::StageInputKind::InvocationId || kind == IR::StageInputKind::PrimitiveId ||
kind == IR::StageInputKind::Layer || kind == IR::StageInputKind::SampleId) {
const auto value = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({spv::OpLoad, TypeI32(state), value, variable});
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, value});
state.builder.AddFunction(spv::OpLoad, TypeI32(state), value, variable);
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
return bits;
}
if (kind == IR::StageInputKind::FragCoord) {
if (kind == IR::StageInputKind::FragCoord || kind == IR::StageInputKind::TessCoord) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain,
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
pointer, variable, ConstantU32(state, component)});
state.builder.AddFunction({spv::OpLoad, TypeF32(state), value, pointer});
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, value});
state.builder.AddFunction(spv::OpAccessChain,
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
pointer, variable, ConstantU32(state, component));
state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer);
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
return bits;
}
if (kind == IR::StageInputKind::BaryCoordSmooth ||
@@ -74,11 +76,11 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain,
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
pointer, variable, ConstantU32(state, component + 1u)});
state.builder.AddFunction({spv::OpLoad, TypeF32(state), value, pointer});
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, value});
state.builder.AddFunction(spv::OpAccessChain,
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
pointer, variable, ConstantU32(state, component + 1u));
state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer);
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
return bits;
}
return EmitInputComponentU32(state, kind, component);
@@ -99,37 +101,37 @@ uint32_t EmitDppWriteCondition(ValueEmitContext& ctx, const IR::DppMoveFlags& fl
const auto bank_ok = state.builder.AllocateId();
const auto row_ok = state.builder.AllocateId();
const auto masks_ok = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpShiftRightLogical, TypeU32(state), bank_shift, lane, ConstantU32(state, 2)});
state.builder.AddFunction(
{spv::OpShiftRightLogical, TypeU32(state), row_shift, lane, ConstantU32(state, 4)});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), bank, bank_shift, ConstantU32(state, 3)});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), row, row_shift, ConstantU32(state, 3)});
state.builder.AddFunction(
{spv::OpShiftLeftLogical, TypeU32(state), bank_bit, ConstantU32(state, 1), bank});
state.builder.AddFunction(
{spv::OpShiftLeftLogical, TypeU32(state), row_bit, ConstantU32(state, 1), row});
state.builder.AddFunction({spv::OpBitwiseAnd, TypeU32(state), bank_hit,
ConstantU32(state, flags.bank_mask), bank_bit});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), row_hit, ConstantU32(state, flags.row_mask), row_bit});
state.builder.AddFunction(
{spv::OpINotEqual, TypeBool(state), bank_ok, bank_hit, ConstantU32(state, 0)});
state.builder.AddFunction(
{spv::OpINotEqual, TypeBool(state), row_ok, row_hit, ConstantU32(state, 0)});
state.builder.AddFunction({spv::OpLogicalAnd, TypeBool(state), masks_ok, bank_ok, row_ok});
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), bank_shift, lane,
ConstantU32(state, 2));
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), row_shift, lane,
ConstantU32(state, 4));
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), bank, bank_shift,
ConstantU32(state, 3));
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row, row_shift,
ConstantU32(state, 3));
state.builder.AddFunction(spv::OpShiftLeftLogical, TypeU32(state), bank_bit,
ConstantU32(state, 1), bank);
state.builder.AddFunction(spv::OpShiftLeftLogical, TypeU32(state), row_bit,
ConstantU32(state, 1), row);
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), bank_hit,
ConstantU32(state, flags.bank_mask), bank_bit);
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row_hit,
ConstantU32(state, flags.row_mask), row_bit);
state.builder.AddFunction(spv::OpINotEqual, TypeBool(state), bank_ok, bank_hit,
ConstantU32(state, 0));
state.builder.AddFunction(spv::OpINotEqual, TypeBool(state), row_ok, row_hit,
ConstantU32(state, 0));
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), masks_ok, bank_ok, row_ok);
uint32_t writable = masks_ok;
if (!flags.bound_control) {
const auto target = EmitDppTargetLane(state, flags.control);
const auto bounded = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpLogicalAnd, TypeBool(state), bounded, writable, target.valid});
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), bounded, writable,
target.valid);
writable = bounded;
}
const auto result = state.builder.AllocateId();
state.builder.AddFunction({spv::OpLogicalAnd, TypeBool(state), result, exec, writable});
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), result, exec, writable);
return result;
}
@@ -138,17 +140,16 @@ uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) {
if (input == nullptr || input->variable_id == 0) {
return ConstantU32(state, 0);
}
if (state.program.stage == ShaderType::Vertex) {
if (state.program.stage == ShaderType::Vertex || state.program.stage == ShaderType::Local) {
return EmitVertexParameterComponentU32(state, *input, chan & 3u);
}
const auto load_per_vertex = [&](uint32_t vertex) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain,
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
pointer, input->variable_id, ConstantU32(state, vertex),
ConstantU32(state, chan & 3u)});
state.builder.AddFunction({spv::OpLoad, TypeF32(state), value, pointer});
state.builder.AddFunction(
spv::OpAccessChain, TypePointer(state, spv::StorageClassInput, TypeF32(state)), pointer,
input->variable_id, ConstantU32(state, vertex), ConstantU32(state, chan & 3u));
state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer);
return value;
};
if (input->per_vertex) {
@@ -161,31 +162,31 @@ uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) {
const auto pointer = state.builder.AllocateId();
const auto weight = state.builder.AllocateId();
const auto product = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain,
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
pointer, barycentric, ConstantU32(state, vertex)});
state.builder.AddFunction({spv::OpLoad, TypeF32(state), weight, pointer});
state.builder.AddFunction(
{spv::OpFMul, TypeF32(state), product, load_per_vertex(vertex), weight});
state.builder.AddFunction(spv::OpAccessChain,
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
pointer, barycentric, ConstantU32(state, vertex));
state.builder.AddFunction(spv::OpLoad, TypeF32(state), weight, pointer);
state.builder.AddFunction(spv::OpFMul, TypeF32(state), product, load_per_vertex(vertex),
weight);
if (vertex == 0u) {
sum = product;
} else {
const auto next = state.builder.AllocateId();
state.builder.AddFunction({spv::OpFAdd, TypeF32(state), next, sum, product});
state.builder.AddFunction(spv::OpFAdd, TypeF32(state), next, sum, product);
sum = next;
}
}
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, sum});
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, sum);
return bits;
}
const auto vector = state.builder.AllocateId();
const auto component = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({spv::OpLoad, TypeF32Vector(state, 4), vector, input->variable_id});
state.builder.AddFunction(
{spv::OpCompositeExtract, TypeF32(state), component, vector, chan & 3u});
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, component});
state.builder.AddFunction(spv::OpLoad, TypeF32Vector(state, 4), vector, input->variable_id);
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), component, vector,
chan & 3u);
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, component);
return bits;
}
@@ -199,11 +200,10 @@ uint32_t EmitInterpolationParameter(ValueEmitContext& ctx, uint32_t attr, uint32
const auto load_vertex = [&](uint32_t vertex) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain,
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
pointer, input->variable_id, ConstantU32(state, vertex),
ConstantU32(state, chan & 3u)});
state.builder.AddFunction({spv::OpLoad, TypeF32(state), value, pointer});
state.builder.AddFunction(
spv::OpAccessChain, TypePointer(state, spv::StorageClassInput, TypeF32(state)), pointer,
input->variable_id, ConstantU32(state, vertex), ConstantU32(state, chan & 3u));
state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer);
return value;
};
@@ -211,11 +211,11 @@ uint32_t EmitInterpolationParameter(ValueEmitContext& ctx, uint32_t attr, uint32
uint32_t value = load_vertex(selected_vertex);
if (!PixelParameterIsCustom(state, attr) && mode < 2u) {
const auto delta = state.builder.AllocateId();
state.builder.AddFunction({spv::OpFSub, TypeF32(state), delta, value, load_vertex(0)});
state.builder.AddFunction(spv::OpFSub, TypeF32(state), delta, value, load_vertex(0));
value = delta;
}
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, value});
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
return bits;
}
@@ -229,8 +229,8 @@ uint32_t MrtOutputMode(const EmitterState& state, const IR::ExportInfo& exp) {
uint32_t ExportRawComponent(ValueEmitContext& ctx, uint32_t vector, uint32_t component) {
const auto value = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{spv::OpCompositeExtract, TypeU32(ctx.state), value, vector, component});
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), value, vector,
component);
return value;
}
@@ -248,22 +248,21 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
}
const auto packed = state.builder.AllocateId();
const auto unpacked = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpCompositeExtract, TypeU32(state), packed, data, pair});
state.builder.AddFunction({spv::OpExtInst, TypeF32Vector(state, 2), unpacked,
GlslStd450(state), unpack, packed});
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), packed, data, pair);
state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 2), unpacked,
GlslStd450(state), unpack, packed);
for (uint32_t lane = 0; lane < 2u; lane++) {
const auto component = pair * 2u + lane;
if (((exp.en >> component) & 1u) != 0u) {
f32[component] = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpCompositeExtract, TypeF32(state), f32[component], unpacked, lane});
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state),
f32[component], unpacked, lane);
}
}
}
const auto vector = state.builder.AllocateId();
state.builder.AddFunction({spv::OpCompositeConstruct, TypeF32Vector(state, 4), vector,
f32[0], f32[1], f32[2], f32[3]});
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 4), vector,
f32[0], f32[1], f32[2], f32[3]);
return vector;
}
uint32_t raw[4] = {
@@ -284,9 +283,9 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
continue;
}
raw[component] = state.builder.AllocateId();
state.builder.AddFunction({spv::OpBitFieldUExtract, TypeU32(state), raw[component],
packed, ConstantU32(state, lane * 16u),
ConstantU32(state, 16)});
state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), raw[component],
packed, ConstantU32(state, lane * 16u),
ConstantU32(state, 16));
}
}
} else {
@@ -298,18 +297,18 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
}
if (uint_output) {
const auto vector = state.builder.AllocateId();
state.builder.AddFunction({spv::OpCompositeConstruct, TypeU32Vector(state, 4), vector,
raw[0], raw[1], raw[2], raw[3]});
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), vector,
raw[0], raw[1], raw[2], raw[3]);
return vector;
}
uint32_t f32[4] {};
for (uint32_t component = 0; component < 4u; component++) {
f32[component] = state.builder.AllocateId();
state.builder.AddFunction({spv::OpBitcast, TypeF32(state), f32[component], raw[component]});
state.builder.AddFunction(spv::OpBitcast, TypeF32(state), f32[component], raw[component]);
}
const auto vector = state.builder.AllocateId();
state.builder.AddFunction({spv::OpCompositeConstruct, TypeF32Vector(state, 4), vector, f32[0],
f32[1], f32[2], f32[3]});
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 4), vector, f32[0],
f32[1], f32[2], f32[3]);
return vector;
}
@@ -325,19 +324,19 @@ void EmitAuxPositionExport(ValueEmitContext& ctx, uint32_t data, const IR::Expor
const auto raw = ExportRawComponent(ctx, data, component);
if (output.layer) {
const auto layer = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), layer, raw, ConstantU32(state, 0x7ffu)});
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), layer, raw,
ConstantU32(state, 0x7ffu));
const auto pointer = state.program.stage == ShaderType::Mesh
? MeshOutputPointer(state, IR::StageOutputKind::Layer)
: state.layer_variable;
state.builder.AddFunction({spv::OpStore, pointer, layer});
state.builder.AddFunction(spv::OpStore, pointer, layer);
}
if (output.viewport) {
// GFX10 MISC.z packs the viewport index in bits 16..19 alongside the layer.
const auto viewport = state.builder.AllocateId();
state.builder.AddFunction({spv::OpBitFieldUExtract, TypeU32(state), viewport, raw,
ConstantU32(state, 16), ConstantU32(state, 4)});
state.builder.AddFunction({spv::OpStore, state.viewport_index_variable, viewport});
state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), viewport, raw,
ConstantU32(state, 16), ConstantU32(state, 4));
state.builder.AddFunction(spv::OpStore, state.viewport_index_variable, viewport);
}
continue;
}
@@ -348,9 +347,9 @@ void EmitAuxPositionExport(ValueEmitContext& ctx, uint32_t data, const IR::Expor
const auto raw = ExportRawComponent(ctx, data, component);
const auto f32 = state.builder.AllocateId();
state.builder.AddFunction({spv::OpBitcast, TypeF32(state), f32, raw});
state.builder.AddFunction(spv::OpBitcast, TypeF32(state), f32, raw);
if (output.point_size) {
state.builder.AddFunction({spv::OpStore, state.point_size_variable, f32});
state.builder.AddFunction(spv::OpStore, state.point_size_variable, f32);
continue;
}
auto StoreDistance = [&](uint32_t variable, uint32_t index) {
@@ -358,10 +357,10 @@ void EmitAuxPositionExport(ValueEmitContext& ctx, uint32_t data, const IR::Expor
return;
}
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain,
TypePointer(state, spv::StorageClassOutput, TypeF32(state)),
pointer, variable, ConstantU32(state, index)});
state.builder.AddFunction({spv::OpStore, pointer, f32});
state.builder.AddFunction(spv::OpAccessChain,
TypePointer(state, spv::StorageClassOutput, TypeF32(state)),
pointer, variable, ConstantU32(state, index));
state.builder.AddFunction(spv::OpStore, pointer, f32);
};
StoreDistance(state.clip_distance_variable, output.clip_distance);
StoreDistance(state.cull_distance_variable, output.cull_distance);
@@ -374,14 +373,14 @@ uint32_t ConvertClipCoordinate(EmitterState& state, uint32_t coordinate, float s
const auto biased = state.builder.AllocateId();
const auto divided = state.builder.AllocateId();
const auto ndc = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpFMul, TypeF32(state), window, coordinate, ConstantF32Value(state, scale)});
state.builder.AddFunction(
{spv::OpFAdd, TypeF32(state), biased, window, ConstantF32Value(state, offset)});
state.builder.AddFunction(
{spv::OpFDiv, TypeF32(state), divided, biased, ConstantF32Value(state, half_extent)});
state.builder.AddFunction(
{spv::OpFSub, TypeF32(state), ndc, divided, ConstantF32Value(state, 1.0f)});
state.builder.AddFunction(spv::OpFMul, TypeF32(state), window, coordinate,
ConstantF32Value(state, scale));
state.builder.AddFunction(spv::OpFAdd, TypeF32(state), biased, window,
ConstantF32Value(state, offset));
state.builder.AddFunction(spv::OpFDiv, TypeF32(state), divided, biased,
ConstantF32Value(state, half_extent));
state.builder.AddFunction(spv::OpFSub, TypeF32(state), ndc, divided,
ConstantF32Value(state, 1.0f));
return ndc;
}
@@ -390,16 +389,16 @@ uint32_t ConvertPositionToClipSpace(EmitterState& state, uint32_t position) {
uint32_t components[4] {};
for (uint32_t i = 0; i < 4; i++) {
components[i] = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpCompositeExtract, TypeF32(state), components[i], position, i});
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), components[i], position,
i);
}
components[0] = ConvertClipCoordinate(state, components[0], transform.scale[0],
transform.offset[0], transform.half_extent[0]);
components[1] = ConvertClipCoordinate(state, components[1], transform.scale[1],
transform.offset[1], transform.half_extent[1]);
const auto converted = state.builder.AllocateId();
state.builder.AddFunction({spv::OpCompositeConstruct, TypeF32Vector(state, 4), converted,
components[0], components[1], components[2], components[3]});
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 4), converted,
components[0], components[1], components[2], components[3]);
return converted;
}
@@ -411,17 +410,17 @@ uint32_t EmitWqmU64(EmitterState& state, uint32_t value) {
const auto merged_two = state.builder.AllocateId();
const auto quad_bits = state.builder.AllocateId();
const auto result = state.builder.AllocateId();
state.builder.AddFunction({spv::OpShiftRightLogical, TypeU64(state), shifted_one, value,
ConstantU64(state, 0x0000000100000001ull)});
state.builder.AddFunction({spv::OpBitwiseOr, TypeU64(state), merged_one, value, shifted_one});
state.builder.AddFunction({spv::OpShiftRightLogical, TypeU64(state), shifted_two, merged_one,
ConstantU64(state, 0x0000000200000002ull)});
state.builder.AddFunction(
{spv::OpBitwiseOr, TypeU64(state), merged_two, merged_one, shifted_two});
state.builder.AddFunction({spv::OpBitwiseAnd, TypeU64(state), quad_bits, merged_two,
ConstantU64(state, 0x1111111111111111ull)});
state.builder.AddFunction({spv::OpIMul, TypeU64(state), result, quad_bits,
ConstantU64(state, 0x0000000f0000000full)});
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU64(state), shifted_one, value,
ConstantU64(state, 0x0000000100000001ull));
state.builder.AddFunction(spv::OpBitwiseOr, TypeU64(state), merged_one, value, shifted_one);
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU64(state), shifted_two, merged_one,
ConstantU64(state, 0x0000000200000002ull));
state.builder.AddFunction(spv::OpBitwiseOr, TypeU64(state), merged_two, merged_one,
shifted_two);
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU64(state), quad_bits, merged_two,
ConstantU64(state, 0x1111111111111111ull));
state.builder.AddFunction(spv::OpIMul, TypeU64(state), result, quad_bits,
ConstantU64(state, 0x0000000f0000000full));
return result;
}
@@ -432,9 +431,9 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
if (state.program.stage == ShaderType::Pixel && exp.vm && state.requirements.pixel_valid_mask &&
state.pixel_valid_mask_variable != 0) {
const auto value = state.builder.AllocateId();
state.builder.AddFunction({spv::OpSelect, TypeU32(state), value, exec,
ConstantU32(state, 1), ConstantU32(state, 0)});
state.builder.AddFunction({spv::OpStore, state.pixel_valid_mask_variable, value});
state.builder.AddFunction(spv::OpSelect, TypeU32(state), value, exec, ConstantU32(state, 1),
ConstantU32(state, 0));
state.builder.AddFunction(spv::OpStore, state.pixel_valid_mask_variable, value);
}
if (exp.kind == IR::ExportTargetKind::Null || exp.en == 0u) {
return;
@@ -443,8 +442,8 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto data = ctx.Arg(inst, 0);
if (exp.kind == IR::ExportTargetKind::Primitive) {
if (state.program.stage == ShaderType::Mesh) {
state.builder.AddFunction(
{spv::OpStore, MeshPrimitivePointer(state), ExportRawComponent(ctx, data, 0)});
state.builder.AddFunction(spv::OpStore, MeshPrimitivePointer(state),
ExportRawComponent(ctx, data, 0));
}
return;
}
@@ -456,19 +455,18 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
if ((exp.en & 1u) != 0u && state.depth_variable != 0) {
const auto raw = ExportRawComponent(ctx, data, 0);
const auto f32 = state.builder.AllocateId();
state.builder.AddFunction({spv::OpBitcast, TypeF32(state), f32, raw});
state.builder.AddFunction({spv::OpStore, state.depth_variable, f32});
state.builder.AddFunction(spv::OpBitcast, TypeF32(state), f32, raw);
state.builder.AddFunction(spv::OpStore, state.depth_variable, f32);
}
if ((exp.en & 4u) != 0u && state.sample_mask_variable != 0) {
const auto raw = ExportRawComponent(ctx, data, 2);
const auto value = state.builder.AllocateId();
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({spv::OpBitcast, TypeI32(state), value, raw});
state.builder.AddFunction(spv::OpBitcast, TypeI32(state), value, raw);
state.builder.AddFunction(
{spv::OpAccessChain,
TypePointer(state, spv::StorageClassOutput, TypeI32(state)), pointer,
state.sample_mask_variable, ConstantU32(state, 0)});
state.builder.AddFunction({spv::OpStore, pointer, value});
spv::OpAccessChain, TypePointer(state, spv::StorageClassOutput, TypeI32(state)),
pointer, state.sample_mask_variable, ConstantU32(state, 0));
state.builder.AddFunction(spv::OpStore, pointer, value);
}
return;
}
@@ -485,9 +483,9 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto mapping = state.input_info.pixel->target_export_mapping[exp.index];
if (!mapping.IsIdentity()) {
const auto mapped = state.builder.AllocateId();
state.builder.AddFunction({spv::OpVectorShuffle, vector_type, mapped, value, value,
mapping.Map(0), mapping.Map(1), mapping.Map(2),
mapping.Map(3)});
state.builder.AddFunction(spv::OpVectorShuffle, vector_type, mapped, value, value,
mapping.Map(0), mapping.Map(1), mapping.Map(2),
mapping.Map(3));
value = mapped;
}
}
@@ -499,17 +497,17 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto kind = exp.kind == IR::ExportTargetKind::Position
? IR::StageOutputKind::Position
: IR::StageOutputKind::Parameter;
state.builder.AddFunction(
{spv::OpStore, MeshOutputPointer(state, kind, exp.index), value});
state.builder.AddFunction(spv::OpStore, MeshOutputPointer(state, kind, exp.index),
value);
} else if (exp.kind == IR::ExportTargetKind::Position) {
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpAccessChain,
TypePointer(state, spv::StorageClassOutput, TypeF32Vector(state, 4)), pointer,
variable, ConstantU32(state, 0)});
state.builder.AddFunction({spv::OpStore, pointer, value});
spv::OpAccessChain,
TypePointer(state, spv::StorageClassOutput, TypeF32Vector(state, 4)), pointer,
variable, ConstantU32(state, 0));
state.builder.AddFunction(spv::OpStore, pointer, value);
} else {
state.builder.AddFunction({spv::OpStore, variable, value});
state.builder.AddFunction(spv::OpStore, variable, value);
}
});
}
@@ -521,11 +519,19 @@ uint32_t EmitIdentity(ValueEmitContext&, uint32_t value) {
void EmitVoid(ValueEmitContext&) {}
void EmitBarrier(EmitterState& state) {
const auto semantics =
spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsWorkgroupMemoryMask;
state.builder.AddFunction({spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, semantics)});
const auto tessellation = state.program.stage == ShaderType::TessellationControl;
const auto memory_scope = tessellation ? spv::ScopeInvocation : spv::ScopeWorkgroup;
const auto semantics = tessellation ? spv::MemorySemanticsMaskNone
: spv::MemorySemanticsAcquireReleaseMask |
spv::MemorySemanticsWorkgroupMemoryMask;
state.builder.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, memory_scope), ConstantU32(state, semantics));
}
uint32_t EmitLaneId(EmitterState& state) {
return state.program.stage == ShaderType::TessellationControl
? EmitBuiltinU32(state, IR::StageInputKind::InvocationId, 0)
: EmitSubgroupLocalInvocationId(state);
}
uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst) {
@@ -536,10 +542,10 @@ uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst) {
ctx.Fail(inst, "invalid mesh draw parameter");
}
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain, TypePushConstantElementPointer(state), pointer,
state.push_constant_variable, ConstantU32(state, 0),
ConstantU32(state, index)});
state.builder.AddFunction({spv::OpLoad, TypeU32(state), result, pointer});
state.builder.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state), pointer,
state.push_constant_variable, ConstantU32(state, 0),
ConstantU32(state, index));
state.builder.AddFunction(spv::OpLoad, TypeU32(state), result, pointer);
return result;
}
@@ -559,7 +565,7 @@ uint32_t EmitGetBuiltin(ValueEmitContext& ctx, IR::Value kind, IR::Value index)
uint32_t EmitUndefU1(EmitterState& state, const IR::Inst& inst) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({spv::OpUndef, TypeId(state, inst.GetType()), result});
state.builder.AddFunction(spv::OpUndef, TypeId(state, inst.GetType()), result);
return result;
}
@@ -574,8 +580,8 @@ uint32_t EmitDppMoveU32(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto ballot = ctx.Ballot(inst.Arg(1));
const auto source_active = EmitBallotLaneActiveBool(state, ballot, target.lane);
const auto can_fetch = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpLogicalAnd, TypeBool(state), can_fetch, target.valid, source_active});
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), can_fetch, target.valid,
source_active);
return EmitNative<spv::OpSelect, IR::Type::U32>(ctx.state, can_fetch, shuffled,
ConstantU32(state, 0));
}
@@ -604,8 +610,8 @@ uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst) {
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto hit = state.builder.AllocateId();
state.builder.AddFunction({spv::OpIEqual, TypeBool(state), hit,
EmitSubgroupLocalInvocationId(state), ctx.Arg(inst, 2)});
state.builder.AddFunction(spv::OpIEqual, TypeBool(state), hit,
EmitSubgroupLocalInvocationId(state), ctx.Arg(inst, 2));
return EmitNative<spv::OpSelect, IR::Type::U32>(ctx.state, hit, ctx.Arg(inst, 1),
ctx.Arg(inst, 0));
}
@@ -624,35 +630,35 @@ uint32_t EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto shifted = state.builder.AllocateId();
const auto index = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), row, subid, ConstantU32(state, 0xfffffff0u)});
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row, subid,
ConstantU32(state, 0xfffffff0u));
if (flags.x16) {
state.builder.AddFunction(
{spv::OpBitwiseXor, TypeU32(state), row_value, row, ConstantU32(state, 16)});
state.builder.AddFunction(spv::OpBitwiseXor, TypeU32(state), row_value, row,
ConstantU32(state, 16));
} else {
state.builder.AddFunction({spv::OpCopyObject, TypeU32(state), row_value, row});
state.builder.AddFunction(spv::OpCopyObject, TypeU32(state), row_value, row);
}
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), lane, subid, ConstantU32(state, 15)});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), lane8, lane, ConstantU32(state, 7)});
state.builder.AddFunction(
{spv::OpShiftLeftLogical, TypeU32(state), shift, lane8, ConstantU32(state, 2)});
state.builder.AddFunction(
{spv::OpUGreaterThanEqual, TypeBool(state), upper, lane, ConstantU32(state, 8)});
state.builder.AddFunction(
{spv::OpSelect, TypeU32(state), selected, upper, ctx.Arg(inst, 2), ctx.Arg(inst, 1)});
state.builder.AddFunction({spv::OpShiftRightLogical, TypeU32(state), shifted, selected, shift});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), index, shifted, ConstantU32(state, 15)});
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), target, row_value, index});
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
ConstantU32(state, 15));
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane8, lane,
ConstantU32(state, 7));
state.builder.AddFunction(spv::OpShiftLeftLogical, TypeU32(state), shift, lane8,
ConstantU32(state, 2));
state.builder.AddFunction(spv::OpUGreaterThanEqual, TypeBool(state), upper, lane,
ConstantU32(state, 8));
state.builder.AddFunction(spv::OpSelect, TypeU32(state), selected, upper, ctx.Arg(inst, 2),
ctx.Arg(inst, 1));
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), shifted, selected, shift);
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), index, shifted,
ConstantU32(state, 15));
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, row_value, index);
const auto shuffled = ctx.Shuffle(inst, 0, target);
uint32_t result = shuffled;
if (!flags.fetch_inactive) {
const auto source_exec = ctx.Shuffle(inst, 3, target);
result = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpSelect, TypeU32(state), result, source_exec, shuffled, ConstantU32(state, 0)});
state.builder.AddFunction(spv::OpSelect, TypeU32(state), result, source_exec, shuffled,
ConstantU32(state, 0));
}
return result;
}
@@ -9,17 +9,17 @@ DppTargetLane EmitDppQuadPermTargetLane(EmitterState& state, uint32_t subid, uin
const auto selected0 = state.builder.AllocateId();
const auto selected = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), quad_base, subid, ConstantU32(state, 0xfffffffcu)});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), lane, subid, ConstantU32(state, 3)});
state.builder.AddFunction(
{spv::OpShiftLeftLogical, TypeU32(state), shift, lane, ConstantU32(state, 1)});
state.builder.AddFunction(
{spv::OpShiftRightLogical, TypeU32(state), selected0, ConstantU32(state, control), shift});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), selected, selected0, ConstantU32(state, 3)});
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), target, quad_base, selected});
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), quad_base, subid,
ConstantU32(state, 0xfffffffcu));
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
ConstantU32(state, 3));
state.builder.AddFunction(spv::OpShiftLeftLogical, TypeU32(state), shift, lane,
ConstantU32(state, 1));
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), selected0,
ConstantU32(state, control), shift);
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), selected, selected0,
ConstantU32(state, 3));
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, quad_base, selected);
return {target, ConstantBool(state, true)};
}
@@ -30,22 +30,22 @@ DppTargetLane EmitDppRowShiftTargetLane(EmitterState& state, uint32_t subid, uin
const auto lane_shifted = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
const auto valid = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), row, subid, ConstantU32(state, 0xfffffff0u)});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), lane, subid, ConstantU32(state, 15)});
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row, subid,
ConstantU32(state, 0xfffffff0u));
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
ConstantU32(state, 15));
if (left) {
state.builder.AddFunction(
{spv::OpIAdd, TypeU32(state), lane_shifted, lane, ConstantU32(state, amount)});
state.builder.AddFunction(
{spv::OpULessThan, TypeBool(state), valid, lane, ConstantU32(state, 16u - amount)});
state.builder.AddFunction(spv::OpIAdd, TypeU32(state), lane_shifted, lane,
ConstantU32(state, amount));
state.builder.AddFunction(spv::OpULessThan, TypeBool(state), valid, lane,
ConstantU32(state, 16u - amount));
} else {
state.builder.AddFunction(
{spv::OpISub, TypeU32(state), lane_shifted, lane, ConstantU32(state, amount)});
state.builder.AddFunction(
{spv::OpUGreaterThanEqual, TypeBool(state), valid, lane, ConstantU32(state, amount)});
state.builder.AddFunction(spv::OpISub, TypeU32(state), lane_shifted, lane,
ConstantU32(state, amount));
state.builder.AddFunction(spv::OpUGreaterThanEqual, TypeBool(state), valid, lane,
ConstantU32(state, amount));
}
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), target, row, lane_shifted});
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, row, lane_shifted);
return {target, valid};
}
@@ -58,18 +58,17 @@ DppTargetLane EmitDppRowRotateRightTargetLane(EmitterState& state, uint32_t subi
const auto plus = state.builder.AllocateId();
const auto selected = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), row, subid, ConstantU32(state, 0xfffffff0u)});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), lane, subid, ConstantU32(state, 15)});
state.builder.AddFunction(
{spv::OpUGreaterThanEqual, TypeBool(state), in_high, lane, ConstantU32(state, amount)});
state.builder.AddFunction(
{spv::OpISub, TypeU32(state), minus, lane, ConstantU32(state, amount)});
state.builder.AddFunction(
{spv::OpIAdd, TypeU32(state), plus, lane, ConstantU32(state, 16u - amount)});
state.builder.AddFunction({spv::OpSelect, TypeU32(state), selected, in_high, minus, plus});
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), target, row, selected});
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row, subid,
ConstantU32(state, 0xfffffff0u));
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
ConstantU32(state, 15));
state.builder.AddFunction(spv::OpUGreaterThanEqual, TypeBool(state), in_high, lane,
ConstantU32(state, amount));
state.builder.AddFunction(spv::OpISub, TypeU32(state), minus, lane, ConstantU32(state, amount));
state.builder.AddFunction(spv::OpIAdd, TypeU32(state), plus, lane,
ConstantU32(state, 16u - amount));
state.builder.AddFunction(spv::OpSelect, TypeU32(state), selected, in_high, minus, plus);
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, row, selected);
return {target, ConstantBool(state, true)};
}
@@ -80,13 +79,13 @@ DppTargetLane EmitDppMirrorTargetLane(EmitterState& state, uint32_t subid, bool
const auto lane = state.builder.AllocateId();
const auto mirrored = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), base, subid, ConstantU32(state, base_mask)});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), lane, subid, ConstantU32(state, lane_mask)});
state.builder.AddFunction(
{spv::OpISub, TypeU32(state), mirrored, ConstantU32(state, lane_mask), lane});
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), target, base, mirrored});
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), base, subid,
ConstantU32(state, base_mask));
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
ConstantU32(state, lane_mask));
state.builder.AddFunction(spv::OpISub, TypeU32(state), mirrored, ConstantU32(state, lane_mask),
lane);
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, base, mirrored);
return {target, ConstantBool(state, true)};
}
@@ -112,8 +111,8 @@ DppTargetLane EmitDppTargetLane(EmitterState& state, uint32_t control) {
}
if (control >= 0x160u && control <= 0x16fu) {
const auto target = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpBitwiseXor, TypeU32(state), target, subid, ConstantU32(state, control & 0xfu)});
state.builder.AddFunction(spv::OpBitwiseXor, TypeU32(state), target, subid,
ConstantU32(state, control & 0xfu));
return {target, ConstantBool(state, true)};
}
return {subid, ConstantBool(state, true)};
@@ -124,8 +123,8 @@ uint32_t EmitSubgroupLocalInvocationId(EmitterState& state) {
EXIT("SubgroupLocalInvocationId was not declared before SPIR-V function emission\n");
}
const auto value = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpLoad, TypeU32(state), value, state.subgroup_local_invocation_id_variable});
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value,
state.subgroup_local_invocation_id_variable);
return state.lane_half == 0 ? value : EmitAddU32(state, value, ConstantU32(state, 32));
}
@@ -154,10 +153,10 @@ uint32_t EmitInputComponentU32(EmitterState& state, IR::StageInputKind kind, uin
}
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain,
TypePointer(state, spv::StorageClassInput, TypeU32(state)), pointer,
variable, ConstantU32(state, component)});
state.builder.AddFunction({spv::OpLoad, TypeU32(state), value, pointer});
state.builder.AddFunction(spv::OpAccessChain,
TypePointer(state, spv::StorageClassInput, TypeU32(state)), pointer,
variable, ConstantU32(state, component));
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
return value;
}
@@ -167,7 +166,7 @@ uint32_t EmitLocalInvocationIndex(EmitterState& state) {
return ConstantU32(state, 0);
}
const auto value = state.builder.AllocateId();
state.builder.AddFunction({spv::OpLoad, TypeU32(state), value, variable});
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, variable);
if (state.lane_count == 2) {
const auto wave_base =
EmitBinaryU32(state, spv::OpBitwiseAnd, value, ConstantU32(state, ~31u));
@@ -184,13 +183,13 @@ uint32_t EmitVertexParameterComponentU32(EmitterState& state, const InputBinding
const auto scalar_type = VertexParameterScalarType(state, kind);
uint32_t raw = state.builder.AllocateId();
if (count == 1u) {
state.builder.AddFunction({spv::OpLoad, scalar_type, raw, input.variable_id});
state.builder.AddFunction(spv::OpLoad, scalar_type, raw, input.variable_id);
} else {
const auto pointer_type = TypePointer(state, spv::StorageClassInput, scalar_type);
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain, pointer_type, pointer, input.variable_id,
ConstantU32(state, component)});
state.builder.AddFunction({spv::OpLoad, scalar_type, raw, pointer});
state.builder.AddFunction(spv::OpAccessChain, pointer_type, pointer, input.variable_id,
ConstantU32(state, component));
state.builder.AddFunction(spv::OpLoad, scalar_type, raw, pointer);
}
if (kind == VertexInputScalarKind::Uint) {
@@ -198,49 +197,48 @@ uint32_t EmitVertexParameterComponentU32(EmitterState& state, const InputBinding
}
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({spv::OpBitcast, TypeU32(state), bits, raw});
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, raw);
return bits;
}
uint32_t EmitSubgroupLaneActiveBool(EmitterState& state, uint32_t lane) {
const auto active_ballot = state.builder.AllocateId();
state.builder.AddFunction({spv::OpGroupNonUniformBallot, TypeU32Vector(state, 4), active_ballot,
ConstantU32(state, spv::ScopeSubgroup), ConstantBool(state, true)});
state.builder.AddFunction(spv::OpGroupNonUniformBallot, TypeU32Vector(state, 4), active_ballot,
ConstantU32(state, spv::ScopeSubgroup), ConstantBool(state, true));
return EmitBallotLaneActiveBool(state, active_ballot, lane);
}
uint32_t EmitBallotLaneActiveBool(EmitterState& state, uint32_t active_ballot, uint32_t lane) {
const auto low = state.builder.AllocateId();
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), low, active_ballot, 0});
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), low, active_ballot, 0);
uint32_t mask = low;
if (state.program.wave_size == 64u) {
const auto high = state.builder.AllocateId();
const auto in_high = state.builder.AllocateId();
const auto selected = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpCompositeExtract, TypeU32(state), high, active_ballot, 1});
state.builder.AddFunction(
{spv::OpUGreaterThanEqual, TypeBool(state), in_high, lane, ConstantU32(state, 32)});
state.builder.AddFunction({spv::OpSelect, TypeU32(state), selected, in_high, high, low});
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), high, active_ballot, 1);
state.builder.AddFunction(spv::OpUGreaterThanEqual, TypeBool(state), in_high, lane,
ConstantU32(state, 32));
state.builder.AddFunction(spv::OpSelect, TypeU32(state), selected, in_high, high, low);
mask = selected;
}
const auto lane_low = state.builder.AllocateId();
const auto bit = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), lane_low, lane, ConstantU32(state, 31)});
state.builder.AddFunction(
{spv::OpShiftLeftLogical, TypeU32(state), bit, ConstantU32(state, 1), lane_low});
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane_low, lane,
ConstantU32(state, 31));
state.builder.AddFunction(spv::OpShiftLeftLogical, TypeU32(state), bit, ConstantU32(state, 1),
lane_low);
const auto hit = state.builder.AllocateId();
const auto active = state.builder.AllocateId();
const auto in_range = state.builder.AllocateId();
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({spv::OpBitwiseAnd, TypeU32(state), hit, mask, bit});
state.builder.AddFunction(
{spv::OpINotEqual, TypeBool(state), active, hit, ConstantU32(state, 0)});
state.builder.AddFunction({spv::OpULessThan, TypeBool(state), in_range, lane,
ConstantU32(state, state.program.wave_size)});
state.builder.AddFunction({spv::OpLogicalAnd, TypeBool(state), ret, active, in_range});
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), hit, mask, bit);
state.builder.AddFunction(spv::OpINotEqual, TypeBool(state), active, hit,
ConstantU32(state, 0));
state.builder.AddFunction(spv::OpULessThan, TypeBool(state), in_range, lane,
ConstantU32(state, state.program.wave_size));
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), ret, active, in_range);
return ret;
}
@@ -90,14 +90,14 @@ uint32_t CubeAxis(EmitterState& state, uint32_t value) {
uint32_t CubeLayer(EmitterState& state, uint32_t value) {
const auto guest = state.builder.AllocateId();
state.builder.AddFunction({spv::OpConvertFToU, TypeU32(state), guest, value});
state.builder.AddFunction(spv::OpConvertFToU, TypeU32(state), guest, value);
const auto padding = Binary(
state, spv::OpShiftLeftLogical, TypeU32(state),
Binary(state, spv::OpShiftRightLogical, TypeU32(state), guest, ConstantU32(state, 3)),
ConstantU32(state, 1));
const auto host = Binary(state, spv::OpISub, TypeU32(state), guest, padding);
const auto result = state.builder.AllocateId();
state.builder.AddFunction({spv::OpConvertUToF, TypeF32(state), result, host});
state.builder.AddFunction(spv::OpConvertUToF, TypeF32(state), result, host);
return result;
}
@@ -118,11 +118,11 @@ uint32_t CoordF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::In
? AddressF32(ctx, mem, address, first + 2u)
: ZeroF32(ctx.state);
if (cube) z = CubeLayer(ctx.state, z);
ctx.state.builder.AddFunction(
{spv::OpCompositeConstruct, TypeF32Vector(ctx.state, 3), result, x, y, z});
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(ctx.state, 3),
result, x, y, z);
} else {
ctx.state.builder.AddFunction(
{spv::OpCompositeConstruct, TypeF32Vector(ctx.state, 2), result, x, y});
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(ctx.state, 2),
result, x, y);
}
return result;
}
@@ -138,11 +138,11 @@ uint32_t CoordU32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::In
if (components == 3u) {
const auto z = mem.image_address_components > 2u ? AddressU32(ctx, mem, address, 2)
: ConstantU32(ctx.state, 0);
ctx.state.builder.AddFunction(
{spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 3), result, x, y, z});
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 3),
result, x, y, z);
} else {
ctx.state.builder.AddFunction(
{spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 2), result, x, y});
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 2),
result, x, y);
}
return result;
}
@@ -193,9 +193,9 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value,
if (dref) return value;
const auto component = gather ? index : DmaskComponent(mem.dmask, index);
const auto result = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({spv::OpCompositeExtract,
ImageScalarType(ctx.state, value_class), result, value,
component});
ctx.state.builder.AddFunction(spv::OpCompositeExtract,
ImageScalarType(ctx.state, value_class), result, value,
component);
return result;
};
for (uint32_t word = 0; word < mem.data_dwords; word++) {
@@ -218,17 +218,16 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value,
ConstantU32(ctx.state, 16u)));
} else {
const auto pair = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{spv::OpCompositeConstruct, TypeF32Vector(ctx.state, 2), pair, low, high});
ctx.state.builder.AddFunction(spv::OpCompositeConstruct,
TypeF32Vector(ctx.state, 2), pair, low, high);
packed[word] = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({spv::OpExtInst, TypeU32(ctx.state), packed[word],
GlslStd450(ctx.state), GLSLstd450PackHalf2x16,
pair});
ctx.state.builder.AddFunction(spv::OpExtInst, TypeU32(ctx.state), packed[word],
GlslStd450(ctx.state), GLSLstd450PackHalf2x16, pair);
}
}
const auto result = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4),
result, packed[0], packed[1], packed[2], packed[3]});
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4),
result, packed[0], packed[1], packed[2], packed[3]);
return result;
}
uint32_t component[4] {};
@@ -238,14 +237,13 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value,
continue;
}
const auto scalar = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({spv::OpCompositeExtract,
ImageScalarType(ctx.state, value_class), scalar, value,
index});
ctx.state.builder.AddFunction(
spv::OpCompositeExtract, ImageScalarType(ctx.state, value_class), scalar, value, index);
component[index] = SampledComponentBits(ctx, scalar, value_class);
}
const auto result = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
component[0], component[1], component[2], component[3]});
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
component[0], component[1], component[2], component[3]);
return result;
}
@@ -256,12 +254,12 @@ uint32_t QueryDimensions(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
const auto image = LoadSampledImageDescriptor(ctx.state, mem.resource);
const auto size = ctx.state.builder.AllocateId();
if (info.multisampled != 0u) {
ctx.state.builder.AddFunction(
{spv::OpImageQuerySize, ImageViewSizeType(ctx.state, dimension), size, image});
ctx.state.builder.AddFunction(spv::OpImageQuerySize,
ImageViewSizeType(ctx.state, dimension), size, image);
} else {
ctx.state.builder.AddFunction({spv::OpImageQuerySizeLod,
ImageViewSizeType(ctx.state, dimension), size, image,
AddressU32(ctx, mem, address, 0)});
ctx.state.builder.AddFunction(spv::OpImageQuerySizeLod,
ImageViewSizeType(ctx.state, dimension), size, image,
AddressU32(ctx, mem, address, 0));
}
const auto components = info.coordinate_components;
uint32_t result[4] = {ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0),
@@ -271,18 +269,18 @@ uint32_t QueryDimensions(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
} else {
for (uint32_t index = 0; index < components; index++) {
result[index] = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{spv::OpCompositeExtract, TypeU32(ctx.state), result[index], size, index});
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state),
result[index], size, index);
}
}
if (info.multisampled == 0u) {
result[3] = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{spv::OpImageQueryLevels, TypeU32(ctx.state), result[3], image});
ctx.state.builder.AddFunction(spv::OpImageQueryLevels, TypeU32(ctx.state), result[3],
image);
}
const auto vector = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), vector,
result[0], result[1], result[2], result[3]});
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), vector,
result[0], result[1], result[2], result[3]);
return vector;
}
@@ -294,11 +292,11 @@ uint32_t PackedOffset(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR
if (components == 1u) return zero;
const auto result = ctx.state.builder.AllocateId();
if (components == 3u) {
ctx.state.builder.AddFunction(
{spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 3), result, zero, zero, zero});
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 3),
result, zero, zero, zero);
} else {
ctx.state.builder.AddFunction(
{spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 2), result, zero, zero});
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 2),
result, zero, zero);
}
return result;
}
@@ -307,18 +305,18 @@ uint32_t PackedOffset(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR
uint32_t values[3] = {zero, zero, zero};
for (uint32_t index = 0; index < components; index++) {
values[index] = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({spv::OpBitFieldSExtract, TypeI32(ctx.state), values[index],
packed, ConstantU32(ctx.state, index * 8u),
ConstantU32(ctx.state, 6)});
ctx.state.builder.AddFunction(spv::OpBitFieldSExtract, TypeI32(ctx.state), values[index],
packed, ConstantU32(ctx.state, index * 8u),
ConstantU32(ctx.state, 6));
}
if (components == 1u) return values[0];
const auto result = ctx.state.builder.AllocateId();
if (components == 3u) {
ctx.state.builder.AddFunction({spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 3),
result, values[0], values[1], values[2]});
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 3),
result, values[0], values[1], values[2]);
} else {
ctx.state.builder.AddFunction(
{spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 2), result, values[0], values[1]});
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeI32Vector(ctx.state, 2),
result, values[0], values[1]);
}
return result;
}
@@ -327,7 +325,7 @@ uint32_t HorizontalOffsets(EmitterState& state, ImageDimension dimension) {
const auto components = ImageDimensionInfoFor(dimension).spatial_components;
const auto count = ConstantU32(state, 4);
const auto element_type = components == 1u ? TypeI32(state) : TypeI32Vector(state, 2);
const auto array_type = state.builder.Type(spv::OpTypeArray, {element_type, count});
const auto array_type = state.builder.Type(spv::OpTypeArray, element_type, count);
uint32_t offsets[4] {};
for (uint32_t index = 0; index < 4u; index++) {
const auto x = ConstantI32(state, static_cast<int32_t>(index) - 1);
@@ -335,11 +333,11 @@ uint32_t HorizontalOffsets(EmitterState& state, ImageDimension dimension) {
offsets[index] = x;
} else {
offsets[index] = state.builder.Constant(
spv::OpConstantComposite, TypeI32Vector(state, 2), {x, ConstantI32(state, 0)});
spv::OpConstantComposite, TypeI32Vector(state, 2), x, ConstantI32(state, 0));
}
}
return state.builder.Constant(spv::OpConstantComposite, array_type,
{offsets[0], offsets[1], offsets[2], offsets[3]});
return state.builder.Constant(spv::OpConstantComposite, array_type, offsets[0], offsets[1],
offsets[2], offsets[3]);
}
uint32_t InverseSwizzle(uint32_t swizzle, uint32_t component) {
@@ -367,15 +365,15 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
if (info.format == Prospero::BufferFormat::kInvalid) return texel;
const auto packed = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(ctx.state), packed, texel, 0u});
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), packed, texel, 0u);
uint32_t components[4] = {ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0),
ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0)};
for (uint32_t component = 0; component < info.component_count; component++) {
components[component] = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({spv::OpBitFieldUExtract, TypeU32(ctx.state),
components[component], packed,
ConstantU32(ctx.state, info.component_bit_offset[component]),
ConstantU32(ctx.state, info.component_bits[component])});
ctx.state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(ctx.state),
components[component], packed,
ConstantU32(ctx.state, info.component_bit_offset[component]),
ConstantU32(ctx.state, info.component_bits[component]));
}
for (uint32_t component = info.component_count; component < 4u; component++) {
components[component] = components[component % info.component_count];
@@ -394,8 +392,8 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
}
}
const auto result = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
selected[0], selected[1], selected[2], selected[3]});
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
selected[0], selected[1], selected[2], selected[3]);
return result;
}
@@ -409,24 +407,24 @@ uint32_t UnpackImageGather(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uin
if (selector < 4u) {
const auto value = ConstantU32(ctx.state, selector == 1u ? 1u : 0u);
return ctx.state.builder.Constant(spv::OpConstantComposite, TypeU32Vector(ctx.state, 4),
{value, value, value, value});
value, value, value, value);
}
uint32_t values[4] {};
for (uint32_t lane = 0; lane < 4u; lane++) {
const auto packed = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{spv::OpCompositeExtract, TypeU32(ctx.state), packed, gathered, lane});
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), packed, gathered,
lane);
values[lane] = ctx.state.builder.AllocateId();
const auto physical = (selector - 4u) % info.component_count;
ctx.state.builder.AddFunction({spv::OpBitFieldUExtract, TypeU32(ctx.state), values[lane],
packed,
ConstantU32(ctx.state, info.component_bit_offset[physical]),
ConstantU32(ctx.state, info.component_bits[physical])});
ctx.state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(ctx.state), values[lane],
packed,
ConstantU32(ctx.state, info.component_bit_offset[physical]),
ConstantU32(ctx.state, info.component_bits[physical]));
}
const auto result = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
values[0], values[1], values[2], values[3]});
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
values[0], values[1], values[2], values[3]);
return result;
}
@@ -436,16 +434,16 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo&
state.builder.RequireCapability(spv::CapabilityImageQuery);
const auto image = LoadSampledImageDescriptor(state, mem.resource);
const auto width = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpImageQuerySizeLod, TypeU32(state), width, image, ConstantU32(state, 0)});
state.builder.AddFunction(spv::OpImageQuerySizeLod, TypeU32(state), width, image,
ConstantU32(state, 0));
const auto width_f32 = state.builder.AllocateId();
state.builder.AddFunction({spv::OpConvertUToF, TypeF32(state), width_f32, width});
state.builder.AddFunction(spv::OpConvertUToF, TypeF32(state), width_f32, width);
const auto left = state.builder.AllocateId();
state.builder.AddFunction({spv::OpExtInst, TypeF32(state), left, GlslStd450(state),
GLSLstd450Floor,
Binary(state, spv::OpFSub, TypeF32(state),
Binary(state, spv::OpFMul, TypeF32(state), coord, width_f32),
ConstantF32(state, 0x3f000000u))});
state.builder.AddFunction(spv::OpExtInst, TypeF32(state), left, GlslStd450(state),
GLSLstd450Floor,
Binary(state, spv::OpFSub, TypeF32(state),
Binary(state, spv::OpFMul, TypeF32(state), coord, width_f32),
ConstantF32(state, 0x3f000000u)));
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler);
const auto vector_type = ImageVectorType(state, numeric_class, 4);
@@ -462,15 +460,15 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo&
ConstantF32(state, index == 0u ? 0x3f000000u : 0x3fc00000u)),
width_f32);
const auto texel = state.builder.AllocateId();
state.builder.AddFunction({spv::OpImageSampleExplicitLod, vector_type, texel, sampled,
sample_coord, spv::ImageOperandsLodMask, ZeroF32(state)});
state.builder.AddFunction(spv::OpImageSampleExplicitLod, vector_type, texel, sampled,
sample_coord, spv::ImageOperandsLodMask, ZeroF32(state));
values[index] = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpCompositeExtract, scalar_type, values[index], texel, component});
state.builder.AddFunction(spv::OpCompositeExtract, scalar_type, values[index], texel,
component);
}
const auto result = state.builder.AllocateId();
state.builder.AddFunction({spv::OpCompositeConstruct, vector_type, result, values[0], values[1],
values[1], values[0]});
state.builder.AddFunction(spv::OpCompositeConstruct, vector_type, result, values[0], values[1],
values[1], values[0]);
return result;
}
@@ -481,8 +479,8 @@ uint32_t PackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32
auto packed = ConstantU32(ctx.state, 0u);
for (uint32_t component = 0; component < info.component_count; component++) {
const auto value = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{spv::OpCompositeExtract, TypeU32(ctx.state), value, texel, component});
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), value, texel,
component);
const auto maximum =
ConstantU32(ctx.state, info.component_bits[component] == 32u
? UINT32_MAX
@@ -490,8 +488,8 @@ uint32_t PackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32
const auto within =
Binary(ctx.state, spv::OpULessThan, TypeBool(ctx.state), value, maximum);
const auto clamped = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{spv::OpSelect, TypeU32(ctx.state), clamped, within, value, maximum});
ctx.state.builder.AddFunction(spv::OpSelect, TypeU32(ctx.state), clamped, within, value,
maximum);
const auto shifted =
info.component_bit_offset[component] == 0u
? clamped
@@ -501,8 +499,8 @@ uint32_t PackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32
}
const auto zero = ConstantU32(ctx.state, 0u);
const auto result = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result, packed, zero, zero, zero});
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
packed, zero, zero, zero);
return result;
}
@@ -516,9 +514,8 @@ uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t d
if (source < 4u && ((dmask >> source) & 1u) != 0u) {
const auto packed_index = DmaskComponentIndex(dmask, source);
raw = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{spv::OpCompositeExtract, TypeU32(ctx.state), raw, data,
mem.data_bits == 16u ? packed_index / 2u : packed_index});
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), raw, data,
mem.data_bits == 16u ? packed_index / 2u : packed_index);
if (mem.data_bits == 16u) {
if ((packed_index & 1u) != 0u) {
raw = Binary(ctx.state, spv::OpShiftRightLogical, TypeU32(ctx.state), raw,
@@ -534,14 +531,14 @@ uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t d
: Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), raw);
}
const auto texel = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{spv::OpCompositeConstruct,
integer ? TypeU32Vector(ctx.state, 4) : TypeF32Vector(ctx.state, 4), texel, values[0],
values[1], values[2], values[3]});
ctx.state.builder.AddFunction(spv::OpCompositeConstruct,
integer ? TypeU32Vector(ctx.state, 4)
: TypeF32Vector(ctx.state, 4),
texel, values[0], values[1], values[2], values[3]);
return PackImageTexel(ctx, mem, texel);
}
uint32_t ImageAtomicOpcode(IR::ValueOpcode opcode) {
spv::Op ImageAtomicOpcode(IR::ValueOpcode opcode) {
switch (opcode) {
case IR::ValueOpcode::ImageAtomicSwap32: return spv::OpAtomicExchange;
case IR::ValueOpcode::ImageAtomicIAdd32: return spv::OpAtomicIAdd;
@@ -550,7 +547,7 @@ uint32_t ImageAtomicOpcode(IR::ValueOpcode opcode) {
case IR::ValueOpcode::ImageAtomicAnd32: return spv::OpAtomicAnd;
case IR::ValueOpcode::ImageAtomicOr32: return spv::OpAtomicOr;
case IR::ValueOpcode::ImageAtomicXor32: return spv::OpAtomicXor;
default: return 0;
default: return spv::OpNop;
}
}
@@ -576,19 +573,19 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler);
const auto lod = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled,
CoordF32(ctx, mem, *address, 0, ImageDimensionInfoFor(dimension).spatial_components)});
spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled,
CoordF32(ctx, mem, *address, 0, ImageDimensionInfoFor(dimension).spatial_components));
uint32_t values[4] = {ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0),
ConstantU32(state, 0)};
for (uint32_t index = 0; index < 2u; index++) {
const auto component = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpCompositeExtract, TypeF32(state), component, lod, index});
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), component, lod,
index);
values[index] = FloatBits(ctx, component);
}
const auto result = state.builder.AllocateId();
state.builder.AddFunction({spv::OpCompositeConstruct, TypeU32Vector(state, 4), result,
values[0], values[1], values[2], values[3]});
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), result,
values[0], values[1], values[2], values[3]);
ctx.Define(inst, result);
return;
}
@@ -607,14 +604,14 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto coord = CoordU32(ctx, mem, *address, dimension);
if (dimension_info.multisampled != 0u) {
state.builder.AddFunction(
{spv::OpImageFetch, ImageVectorType(state, numeric_class, 4), color,
descriptor, coord, spv::ImageOperandsSampleMask,
AddressU32(ctx, mem, *address, dimension_info.coordinate_components)});
spv::OpImageFetch, ImageVectorType(state, numeric_class, 4), color,
descriptor, coord, spv::ImageOperandsSampleMask,
AddressU32(ctx, mem, *address, dimension_info.coordinate_components));
} else {
state.builder.AddFunction({spv::OpImageFetch,
ImageVectorType(state, numeric_class, 4), color,
descriptor, coord, spv::ImageOperandsLodMask,
LodU32(ctx, mem, *address, dimension)});
state.builder.AddFunction(spv::OpImageFetch,
ImageVectorType(state, numeric_class, 4), color,
descriptor, coord, spv::ImageOperandsLodMask,
LodU32(ctx, mem, *address, dimension));
}
return ResultVector(ctx, UnpackImageTexel(ctx, mem, color), numeric_class,
false, mem);
@@ -711,7 +708,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
HasFlag(mem, Decoder::ImageSampleFlagLod) ||
HasFlag(mem, Decoder::ImageSampleFlagLevelZero) ||
state.program.stage != ShaderType::Pixel;
uint32_t opcode = spv::OpImageSampleImplicitLod;
auto opcode = spv::OpImageSampleImplicitLod;
if (explicit_lod) {
opcode = dref ? spv::OpImageSampleDrefExplicitLod : spv::OpImageSampleExplicitLod;
} else if (dref) {
@@ -748,15 +745,15 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto EmitSample = [&](uint32_t resource) {
const auto sampled = MakeSampledImage(state, resource, mem.sampler);
const auto sample = state.builder.AllocateId();
std::vector<uint32_t> words {opcode, result_type, sample, sampled, coord};
std::vector<uint32_t> sample_operands {result_type, sample, sampled, coord};
if (dref) {
words.push_back(dref_value);
sample_operands.push_back(dref_value);
}
if (operand_mask != 0u) {
words.push_back(operand_mask);
words.insert(words.end(), operands.begin(), operands.end());
sample_operands.push_back(operand_mask);
sample_operands.insert(sample_operands.end(), operands.begin(), operands.end());
}
state.builder.AddFunction(words);
state.builder.AddFunction(opcode, sample_operands);
return sample;
};
if (image.indirect_root != mem.resource) {
@@ -785,11 +782,11 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
}
const auto LoadMapping = [&](uint32_t index) {
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain, TypeStorageBufferElementPointer(state),
pointer, state.flattened_srt_variable, ConstantU32(state, 0),
index});
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state),
pointer, state.flattened_srt_variable, ConstantU32(state, 0),
index);
const auto value = state.builder.AllocateId();
state.builder.AddFunction({spv::OpLoad, TypeU32(state), value, pointer});
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
return value;
};
const auto mapping = ConstantU32(state, image.indirect_mapping_offset);
@@ -802,9 +799,9 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto mid = Binary(state, spv::OpShiftRightLogical, TypeU32(state),
Binary(state, spv::OpIAdd, TypeU32(state), low, high),
ConstantU32(state, 1u));
const auto probe = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpSelect, TypeU32(state), probe, active, mid, ConstantU32(state, 0u)});
const auto probe = state.builder.AllocateId();
state.builder.AddFunction(spv::OpSelect, TypeU32(state), probe, active, mid,
ConstantU32(state, 0u));
const auto entry = Binary(state, spv::OpIAdd, TypeU32(state), mapping,
Binary(state, spv::OpIAdd, TypeU32(state),
Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
@@ -816,8 +813,8 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto equal = Binary(state, spv::OpIEqual, TypeBool(state), mapped_key, key);
const auto match = Binary(state, spv::OpLogicalAnd, TypeBool(state), active, equal);
const auto next_selected = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpSelect, TypeU32(state), next_selected, match, candidate, selected});
state.builder.AddFunction(spv::OpSelect, TypeU32(state), next_selected, match,
candidate, selected);
selected = next_selected;
const auto less = Binary(state, spv::OpULessThan, TypeBool(state), mapped_key, key);
const auto take_upper = Binary(state, spv::OpLogicalAnd, TypeBool(state), active, less);
@@ -825,12 +822,12 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
Unary(state, spv::OpLogicalNot, TypeBool(state), less));
const auto next_low = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpSelect, TypeU32(state), next_low, take_upper,
Binary(state, spv::OpIAdd, TypeU32(state), mid, ConstantU32(state, 1u)), low});
spv::OpSelect, TypeU32(state), next_low, take_upper,
Binary(state, spv::OpIAdd, TypeU32(state), mid, ConstantU32(state, 1u)), low);
low = next_low;
const auto next_high = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpSelect, TypeU32(state), next_high, take_lower, mid, high});
state.builder.AddFunction(spv::OpSelect, TypeU32(state), next_high, take_lower, mid,
high);
high = next_high;
}
const auto default_label = state.builder.AllocateId();
@@ -842,19 +839,19 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
switch_words.push_back(candidate);
switch_words.push_back(labels[candidate - 1u]);
}
state.builder.AddFunction(
{spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone});
state.builder.AddFunction(spv::OpSelectionMerge, merge_label,
spv::SelectionControlMaskNone);
state.builder.AddFunction(switch_words);
std::vector<uint32_t> phi_words {spv::OpPhi, result_type, state.builder.AllocateId()};
EmitLabel(state, default_label);
phi_words.push_back(EmitSample(image.indirect_resources[0]));
phi_words.push_back(default_label);
state.builder.AddFunction({spv::OpBranch, merge_label});
state.builder.AddFunction(spv::OpBranch, merge_label);
for (uint32_t candidate = 1; candidate < image.indirect_resources.size(); candidate++) {
EmitLabel(state, labels[candidate - 1u]);
phi_words.push_back(EmitSample(image.indirect_resources[candidate]));
phi_words.push_back(labels[candidate - 1u]);
state.builder.AddFunction({spv::OpBranch, merge_label});
state.builder.AddFunction(spv::OpBranch, merge_label);
}
EmitLabel(state, merge_label);
state.builder.AddFunction(phi_words);
@@ -866,21 +863,21 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
return;
}
const auto atomic_opcode = ImageAtomicOpcode(op);
if (atomic_opcode != 0u) {
if (atomic_opcode != spv::OpNop) {
const auto dimension = image.dimension;
ctx.Define(inst, EmitValueOrZeroIfCondition(state, ctx.Arg(inst, 3), [&]() {
const auto pointer = state.builder.AllocateId();
const auto pointer = state.builder.AllocateId();
const auto pointer_type = state.builder.Type(
spv::OpTypePointer, {spv::StorageClassImage, TypeU32(state)});
state.builder.AddFunction(
{spv::OpImageTexelPointer, pointer_type, pointer,
StorageImageDescriptorPointer(state, mem.resource),
CoordU32(ctx, mem, *address, dimension), ConstantU32(state, 0)});
spv::OpTypePointer, spv::StorageClassImage, TypeU32(state));
state.builder.AddFunction(spv::OpImageTexelPointer, pointer_type, pointer,
StorageImageDescriptorPointer(state, mem.resource),
CoordU32(ctx, mem, *address, dimension),
ConstantU32(state, 0));
const auto old = state.builder.AllocateId();
state.builder.AddFunction({atomic_opcode, TypeU32(state), old, pointer,
ConstantU32(state, spv::ScopeDevice),
ConstantU32(state, spv::MemorySemanticsMaskNone),
ctx.Arg(inst, 2)});
state.builder.AddFunction(atomic_opcode, TypeU32(state), old, pointer,
ConstantU32(state, spv::ScopeDevice),
ConstantU32(state, spv::MemorySemanticsMaskNone),
ctx.Arg(inst, 2));
EmitDeviceAtomicMemoryBarrier(state);
return old;
}));
@@ -155,6 +155,8 @@ inline constexpr auto EmitInstPrefetch = EmitVoid;
void EmitBarrier(EmitterState& state);
void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
void EmitSetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetUserData(EmitterState& state, IR::ScalarReg reg);
uint32_t EmitGetBuiltin(ValueEmitContext& ctx, IR::Value kind, IR::Value index);
uint32_t EmitUndefU1(EmitterState& state, const IR::Inst& inst);
@@ -165,7 +167,7 @@ inline constexpr auto EmitUndefU64 = EmitUndefU1;
uint32_t EmitDppMoveU32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitWqmU64(EmitterState& state, uint32_t value);
inline constexpr auto EmitLaneId = EmitSubgroupLocalInvocationId;
uint32_t EmitLaneId(EmitterState& state);
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate);
uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst);
@@ -271,6 +273,7 @@ inline constexpr auto EmitImageAtomicOr32 = EmitImage;
inline constexpr auto EmitImageAtomicXor32 = EmitImage;
void EmitUnreachable(ValueEmitContext& ctx, const IR::Inst& inst);
inline constexpr auto EmitPhi = EmitUnreachable;
inline constexpr auto EmitTessellationBase = EmitUnreachable;
inline constexpr auto EmitGetThreadBitScalarRegister = EmitUnreachable;
inline constexpr auto EmitSetThreadBitScalarRegister = EmitUnreachable;
inline constexpr auto EmitGetScalarMaskTag = EmitUnreachable;
@@ -80,6 +80,10 @@ struct EmitterState {
Builder builder;
const IR::Program& program;
ShaderStageInputInfo input_info;
std::array<uint32_t, 6> tess_variables {};
uint32_t tess_inner_variable = 0;
uint32_t tess_patch_base = 0;
const SpirvRequirements requirements;
uint32_t lane_count = 1;
uint32_t lane_half = 0;
@@ -151,7 +155,7 @@ uint32_t TypeU32ElementPointer(EmitterState& state, spv::StorageClass storage_cl
inline void EmitLabel(EmitterState& state, uint32_t label) {
state.current_label = label;
state.builder.AddFunction({spv::OpLabel, label});
state.builder.AddFunction(spv::OpLabel, label);
}
uint32_t TypeId(EmitterState& state, IR::Type type);
@@ -160,7 +164,7 @@ uint32_t TypeId(EmitterState& state, IR::Type type);
template <spv::Op opcode, IR::Type type, typename... Args>
uint32_t EmitNative(EmitterState& state, Args... args) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({opcode, TypeId(state, type), result, args...});
state.builder.AddFunction(opcode, TypeId(state, type), result, args...);
return result;
}
@@ -171,23 +175,23 @@ uint32_t EmitGlsl(EmitterState& state, Args... args) {
return EmitNative<spv::OpExtInst, type>(state, GlslStd450(state), opcode, args...);
}
inline uint32_t Unary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t value) {
inline uint32_t Unary(EmitterState& state, spv::Op opcode, uint32_t type, uint32_t value) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({opcode, type, result, value});
state.builder.AddFunction(opcode, type, result, value);
return result;
}
inline uint32_t Binary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t lhs,
inline uint32_t Binary(EmitterState& state, spv::Op opcode, uint32_t type, uint32_t lhs,
uint32_t rhs) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({opcode, type, result, lhs, rhs});
state.builder.AddFunction(opcode, type, result, lhs, rhs);
return result;
}
inline uint32_t Select(EmitterState& state, uint32_t type, uint32_t condition, uint32_t true_value,
uint32_t false_value) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({spv::OpSelect, type, result, condition, true_value, false_value});
state.builder.AddFunction(spv::OpSelect, type, result, condition, true_value, false_value);
return result;
}
@@ -313,7 +317,11 @@ uint32_t ConstantU64(EmitterState& state, uint64_t value);
uint32_t ConstantU32CompositeZero(EmitterState& state, uint32_t components);
uint32_t DefineInterfaceVariable(EmitterState& state, uint32_t type, spv::StorageClass storage,
const char* name);
void DefineModule(EmitterState& state);
void DefineTessellationInterfaces(EmitterState& state);
void DefineTessellationExecutionModes(EmitterState& state);
void DefineMeshOutputs(EmitterState& state);
void EmitMeshEntryPoint(EmitterState& state);
void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst);
@@ -350,7 +358,7 @@ uint32_t EmitSubgroupLaneActiveBool(EmitterState& state, uint32_t lane);
inline constexpr auto EmitAddU32 = EmitNative<spv::OpIAdd, IR::Type::U32, uint32_t, uint32_t>;
uint32_t EmitBinaryU32(EmitterState& state, uint32_t opcode, uint32_t lhs, uint32_t rhs);
uint32_t EmitBinaryU32(EmitterState& state, spv::Op opcode, uint32_t lhs, uint32_t rhs);
uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index);
@@ -417,7 +425,7 @@ uint32_t EmitShiftRightConstant(EmitterState& state, uint32_t value, uint32_t sh
inline constexpr auto EmitOrU32 = EmitNative<spv::OpBitwiseOr, IR::Type::U32, uint32_t, uint32_t>;
uint32_t EmitCompareU32Constant(EmitterState& state, uint32_t opcode, uint32_t value,
uint32_t EmitCompareU32Constant(EmitterState& state, spv::Op opcode, uint32_t value,
uint32_t constant);
uint32_t EmitSubConstantMinusU32(EmitterState& state, uint32_t constant, uint32_t value);
@@ -474,11 +482,11 @@ template <typename Fn>
void EmitIfCondition(EmitterState& state, uint32_t condition, Fn&& fn) {
const auto then_label = state.builder.AllocateId();
const auto merge_label = state.builder.AllocateId();
state.builder.AddFunction({spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone});
state.builder.AddFunction({spv::OpBranchConditional, condition, then_label, merge_label});
state.builder.AddFunction(spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
state.builder.AddFunction(spv::OpBranchConditional, condition, then_label, merge_label);
EmitLabel(state, then_label);
fn();
state.builder.AddFunction({spv::OpBranch, merge_label});
state.builder.AddFunction(spv::OpBranch, merge_label);
EmitLabel(state, merge_label);
}
@@ -489,19 +497,19 @@ uint32_t EmitValueOrDefaultIfCondition(EmitterState& state, uint32_t condition,
const auto then_exit = state.builder.AllocateId();
const auto else_label = state.builder.AllocateId();
const auto merge_label = state.builder.AllocateId();
state.builder.AddFunction({spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone});
state.builder.AddFunction({spv::OpBranchConditional, condition, then_label, else_label});
state.builder.AddFunction(spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
state.builder.AddFunction(spv::OpBranchConditional, condition, then_label, else_label);
EmitLabel(state, then_label);
const auto then_value = fn();
state.builder.AddFunction({spv::OpBranch, then_exit});
state.builder.AddFunction(spv::OpBranch, then_exit);
EmitLabel(state, then_exit);
state.builder.AddFunction({spv::OpBranch, merge_label});
state.builder.AddFunction(spv::OpBranch, merge_label);
EmitLabel(state, else_label);
state.builder.AddFunction({spv::OpBranch, merge_label});
state.builder.AddFunction(spv::OpBranch, merge_label);
EmitLabel(state, merge_label);
const auto value = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpPhi, type, value, then_value, then_exit, default_value, else_label});
state.builder.AddFunction(spv::OpPhi, type, value, then_value, then_exit, default_value,
else_label);
return value;
}
@@ -528,30 +536,29 @@ uint32_t AtomicUpdate(EmitterState& state, uint32_t pointer, IR::ResourceKind ki
const auto initial = state.builder.AllocateId();
const auto observed = state.builder.AllocateId();
const auto exchanged = state.builder.AllocateId();
state.builder.AddFunction({spv::OpBranch, preheader});
state.builder.AddFunction(spv::OpBranch, preheader);
EmitLabel(state, preheader);
state.builder.AddFunction({spv::OpAtomicLoad, TypeU32(state), initial, pointer,
ConstantU32(state, scope),
ConstantU32(state, spv::MemorySemanticsMaskNone)});
state.builder.AddFunction({spv::OpBranch, header});
state.builder.AddFunction(spv::OpAtomicLoad, TypeU32(state), initial, pointer,
ConstantU32(state, scope),
ConstantU32(state, spv::MemorySemanticsMaskNone));
state.builder.AddFunction(spv::OpBranch, header);
EmitLabel(state, header);
state.builder.AddFunction(
{spv::OpPhi, TypeU32(state), observed, initial, preheader, exchanged, cont});
state.builder.AddFunction(spv::OpPhi, TypeU32(state), observed, initial, preheader, exchanged,
cont);
const auto next = desired(observed);
state.builder.AddFunction({spv::OpAtomicCompareExchange, TypeU32(state), exchanged, pointer,
ConstantU32(state, scope),
ConstantU32(state, spv::MemorySemanticsMaskNone),
ConstantU32(state, spv::MemorySemanticsMaskNone), next, observed});
state.builder.AddFunction(spv::OpAtomicCompareExchange, TypeU32(state), exchanged, pointer,
ConstantU32(state, scope),
ConstantU32(state, spv::MemorySemanticsMaskNone),
ConstantU32(state, spv::MemorySemanticsMaskNone), next, observed);
const auto success = state.builder.AllocateId();
state.builder.AddFunction({spv::OpIEqual, TypeBool(state), success, exchanged, observed});
state.builder.AddFunction({spv::OpLoopMerge, merge, cont, spv::LoopControlMaskNone});
state.builder.AddFunction({spv::OpBranchConditional, success, merge, cont});
state.builder.AddFunction(spv::OpIEqual, TypeBool(state), success, exchanged, observed);
state.builder.AddFunction(spv::OpLoopMerge, merge, cont, spv::LoopControlMaskNone);
state.builder.AddFunction(spv::OpBranchConditional, success, merge, cont);
EmitLabel(state, cont);
state.builder.AddFunction({spv::OpBranch, header});
state.builder.AddFunction(spv::OpBranch, header);
EmitLabel(state, merge);
state.builder.AddFunction(
{spv::OpMemoryBarrier, ConstantU32(state, scope),
ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask | memory)});
state.builder.AddFunction(spv::OpMemoryBarrier, ConstantU32(state, scope),
ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask | memory));
return observed;
}
@@ -17,11 +17,11 @@ uint32_t EmitDsMaskedLaneRead(EmitterState& state, uint32_t source, uint32_t tar
target = Binary(state, spv::OpBitwiseAnd, TypeU32(state), target, ConstantU32(state, 31));
}
const auto shuffled = state.builder.AllocateId();
state.builder.AddFunction({spv::OpGroupNonUniformShuffle, TypeU32(state), shuffled,
ConstantU32(state, spv::ScopeSubgroup), source, target});
state.builder.AddFunction(spv::OpGroupNonUniformShuffle, TypeU32(state), shuffled,
ConstantU32(state, spv::ScopeSubgroup), source, target);
const auto source_exec = state.builder.AllocateId();
state.builder.AddFunction({spv::OpGroupNonUniformShuffle, TypeBool(state), source_exec,
ConstantU32(state, spv::ScopeSubgroup), exec, target});
state.builder.AddFunction(spv::OpGroupNonUniformShuffle, TypeBool(state), source_exec,
ConstantU32(state, spv::ScopeSubgroup), exec, target);
const auto source_active =
AndCondition(state, source_exec, EmitSubgroupLaneActiveBool(state, target));
return Select(state, TypeU32(state), source_active, shuffled, ConstantU32(state, 0));
@@ -107,9 +107,9 @@ uint32_t ScratchByteAddress(ValueEmitContext& ctx, const IR::MemoryInfo& mem, ui
}
uint32_t ConstantDeviceAddress(EmitterState& state, uint64_t value) {
return state.builder.Constant(
spv::OpConstant, TypeScalarU64(state),
{static_cast<uint32_t>(value), static_cast<uint32_t>(value >> 32u)});
return state.builder.Constant(spv::OpConstant, TypeScalarU64(state),
static_cast<uint32_t>(value),
static_cast<uint32_t>(value >> 32u));
}
uint32_t DeviceAddressFromWords(EmitterState& state, uint32_t low, uint32_t high) {
@@ -153,8 +153,8 @@ uint32_t GuestAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Mem
uint32_t FaultElementPointer(EmitterState& state, uint32_t index) {
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
state.fault_buffer_variable, ConstantU32(state, 0), index});
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
state.fault_buffer_variable, ConstantU32(state, 0), index);
return pointer;
}
@@ -166,16 +166,16 @@ void RecordBdaFault(EmitterState& state, uint32_t page) {
Binary(state, spv::OpBitwiseAnd, TypeU32(state), page, ConstantU32(state, 31)));
const auto pointer = FaultElementPointer(state, word);
const auto value = state.builder.AllocateId();
state.builder.AddFunction({spv::OpLoad, TypeU32(state), value, pointer});
state.builder.AddFunction(
{spv::OpStore, pointer, Binary(state, spv::OpBitwiseOr, TypeU32(state), value, bit)});
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
state.builder.AddFunction(spv::OpStore, pointer,
Binary(state, spv::OpBitwiseOr, TypeU32(state), value, bit));
}
uint32_t GetBdaPointer(ValueEmitContext& ctx, uint32_t address) {
auto& state = ctx.state;
const auto result = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpFunctionCall, TypeScalarU64(state), result, state.bda_pointer_function, address});
state.builder.AddFunction(spv::OpFunctionCall, TypeScalarU64(state), result,
state.bda_pointer_function, address);
return result;
}
@@ -186,11 +186,12 @@ uint32_t LoadBdaDword(ValueEmitContext& ctx, uint32_t address) {
Binary(state, spv::OpINotEqual, TypeBool(state), bda, ConstantDeviceAddress(state, 0));
return EmitValueOrZeroIfCondition(state, present, [&]() {
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpConvertUToPtr, TypePhysicalU32Pointer(state), pointer, bda});
const auto value = state.builder.AllocateId();
state.builder.AddFunction({spv::OpLoad, TypeU32(state), value, pointer,
spv::MemoryAccessAlignedMask, sizeof(uint32_t)});
state.builder.AddFunction(spv::OpConvertUToPtr, TypePhysicalU32Pointer(state), pointer,
bda);
const auto value = state.builder.AllocateId();
constexpr uint32_t alignment = sizeof(uint32_t);
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer,
spv::MemoryAccessAlignedMask, alignment);
return value;
});
}
@@ -306,7 +307,7 @@ uint32_t LoadWordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& res
uint32_t index) {
const auto value = ctx.state.builder.AllocateId();
const auto pointer = EmitMemoryElementPointer(ctx.state, resource, index);
ctx.state.builder.AddFunction({spv::OpLoad, TypeU32(ctx.state), value, pointer});
ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), value, pointer);
return value;
}
@@ -401,11 +402,11 @@ uint32_t LoadFormattedComponent(ValueEmitContext& ctx, const IR::MemoryInfo& mem
const auto source_value =
type == TypeI32(ctx.state) ? Unary(ctx.state, spv::OpBitcast, type, raw) : raw;
const auto extracted = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{IsSignedFormatComponent(info.type) ? spv::OpBitFieldSExtract : spv::OpBitFieldUExtract,
type, extracted, source_value,
ConstantU32(ctx.state, info.component_bit_offset[component]),
ConstantU32(ctx.state, bits)});
ctx.state.builder.AddFunction(IsSignedFormatComponent(info.type) ? spv::OpBitFieldSExtract
: spv::OpBitFieldUExtract,
type, extracted, source_value,
ConstantU32(ctx.state, info.component_bit_offset[component]),
ConstantU32(ctx.state, bits));
raw = type == TypeI32(ctx.state)
? Unary(ctx.state, spv::OpBitcast, TypeU32(ctx.state), extracted)
: extracted;
@@ -465,8 +466,8 @@ void StoreSubwordInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
};
if (mem.kind == IR::ResourceKind::Scratch) {
const auto old = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({spv::OpLoad, TypeU32(ctx.state), old, pointer});
ctx.state.builder.AddFunction({spv::OpStore, pointer, merge(old)});
ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), old, pointer);
ctx.state.builder.AddFunction(spv::OpStore, pointer, merge(old));
} else {
AtomicUpdate(ctx.state, pointer, mem.kind, merge);
}
@@ -495,15 +496,15 @@ void StoreWordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Me
const MemoryResourceAccess& resource, uint32_t data) {
const auto index = EmitMemoryElementIndex(ctx.state, resource, DwordIndex(ctx, inst, mem));
EmitIfCondition(ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
ctx.state.builder.AddFunction(
{spv::OpStore, EmitMemoryElementPointer(ctx.state, resource, index), data});
ctx.state.builder.AddFunction(spv::OpStore,
EmitMemoryElementPointer(ctx.state, resource, index), data);
});
}
void StoreWordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& resource, uint32_t index,
uint32_t data) {
ctx.state.builder.AddFunction(
{spv::OpStore, EmitMemoryElementPointer(ctx.state, resource, index), data});
ctx.state.builder.AddFunction(spv::OpStore,
EmitMemoryElementPointer(ctx.state, resource, index), data);
}
void StoreWord(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem) {
@@ -538,7 +539,7 @@ void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Memor
});
}
uint32_t SpirvAtomicOpcode(IR::ValueOpcode opcode) {
spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode) {
switch (opcode) {
case IR::ValueOpcode::BufferAtomicCmpSwap32: return spv::OpAtomicCompareExchange;
case IR::ValueOpcode::BufferAtomicSwap32:
@@ -563,7 +564,7 @@ uint32_t SpirvAtomicOpcode(IR::ValueOpcode opcode) {
case IR::ValueOpcode::SharedAtomicOr32: return spv::OpAtomicOr;
case IR::ValueOpcode::BufferAtomicXor32:
case IR::ValueOpcode::SharedAtomicXor32: return spv::OpAtomicXor;
default: return 0;
default: return spv::OpNop;
}
}
@@ -574,15 +575,14 @@ uint32_t EmitAtomicOperation(ValueEmitContext& ctx, const IR::Inst& inst, uint32
const auto desired = ctx.Arg(inst, inst.NumArgs() - 3);
const auto comparator = ctx.Arg(inst, inst.NumArgs() - 2);
ctx.state.builder.AddFunction(
{spv::OpAtomicCompareExchange, TypeU32(ctx.state), old, pointer,
ConstantU32(ctx.state, scope), ConstantU32(ctx.state, spv::MemorySemanticsMaskNone),
ConstantU32(ctx.state, spv::MemorySemanticsMaskNone), desired, comparator});
spv::OpAtomicCompareExchange, TypeU32(ctx.state), old, pointer,
ConstantU32(ctx.state, scope), ConstantU32(ctx.state, spv::MemorySemanticsMaskNone),
ConstantU32(ctx.state, spv::MemorySemanticsMaskNone), desired, comparator);
} else {
const auto value = ctx.Arg(inst, inst.NumArgs() - 2);
ctx.state.builder.AddFunction({SpirvAtomicOpcode(inst.GetOpcode()), TypeU32(ctx.state), old,
pointer, ConstantU32(ctx.state, scope),
ConstantU32(ctx.state, spv::MemorySemanticsMaskNone),
value});
ctx.state.builder.AddFunction(SpirvAtomicOpcode(inst.GetOpcode()), TypeU32(ctx.state), old,
pointer, ConstantU32(ctx.state, scope),
ConstantU32(ctx.state, spv::MemorySemanticsMaskNone), value);
}
return old;
}
@@ -782,8 +782,8 @@ void StoreWideBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
EmitIfCondition(state, plan.in_bounds, [&]() {
for (uint32_t component = 0; component < components; component++) {
const auto data = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpCompositeExtract, TypeU32(state), data, composite, component});
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), data,
composite, component);
StoreFormattedInBounds(ctx, mem, plan, component, data);
}
});
@@ -791,8 +791,8 @@ void StoreWideBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
}
for (uint32_t component = 0; component < components; component++) {
const auto data = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpCompositeExtract, TypeU32(state), data, composite, component});
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), data, composite,
component);
StoreWordPrepared(ctx, inst, RebaseRawComponent(mem, component), resource, data);
}
});
@@ -851,49 +851,49 @@ void DefineGetBdaPointer(EmitterState& state) {
return;
}
const auto type = TypeScalarU64(state);
const auto function_type = state.builder.Type(spv::OpTypeFunction, {type, type});
const auto function_type = state.builder.Type(spv::OpTypeFunction, type, type);
state.bda_pointer_function = state.builder.AllocateId();
const auto address = state.builder.AllocateId();
const auto entry_label = state.builder.AllocateId();
state.builder.AddName(state.bda_pointer_function, "get_bda_pointer");
state.builder.AddFunction({spv::OpFunction, type, state.bda_pointer_function,
spv::FunctionControlMaskNone, function_type});
state.builder.AddFunction({spv::OpFunctionParameter, type, address});
state.builder.AddFunction(spv::OpFunction, type, state.bda_pointer_function,
spv::FunctionControlMaskNone, function_type);
state.builder.AddFunction(spv::OpFunctionParameter, type, address);
EmitLabel(state, entry_label);
const auto page64 = Binary(state, spv::OpShiftRightLogical, type, address,
ConstantDeviceAddress(state, BufferCache::CACHING_PAGEBITS));
const auto page = Unary(state, spv::OpUConvert, TypeU32(state), page64);
const auto entry_pointer = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain, TypeStorageBufferU64ElementPointer(state),
entry_pointer, state.bda_pagetable_variable, ConstantU32(state, 0),
page});
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferU64ElementPointer(state),
entry_pointer, state.bda_pagetable_variable, ConstantU32(state, 0),
page);
const auto base = state.builder.AllocateId();
state.builder.AddFunction({spv::OpLoad, type, base, entry_pointer});
state.builder.AddFunction(spv::OpLoad, type, base, entry_pointer);
const auto missing =
Binary(state, spv::OpIEqual, TypeBool(state), base, ConstantDeviceAddress(state, 0));
const auto fault_label = state.builder.AllocateId();
const auto available_label = state.builder.AllocateId();
const auto merge_label = state.builder.AllocateId();
state.builder.AddFunction({spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone});
state.builder.AddFunction({spv::OpBranchConditional, missing, fault_label, available_label});
state.builder.AddFunction(spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
state.builder.AddFunction(spv::OpBranchConditional, missing, fault_label, available_label);
EmitLabel(state, fault_label);
RecordBdaFault(state, page);
state.builder.AddFunction({spv::OpBranch, merge_label});
state.builder.AddFunction(spv::OpBranch, merge_label);
EmitLabel(state, available_label);
const auto offset = Binary(state, spv::OpBitwiseAnd, type, address,
ConstantDeviceAddress(state, BufferCache::CACHING_PAGESIZE - 1));
const auto available = Binary(state, spv::OpIAdd, type, base, offset);
state.builder.AddFunction({spv::OpBranch, merge_label});
state.builder.AddFunction(spv::OpBranch, merge_label);
EmitLabel(state, merge_label);
const auto result = state.builder.AllocateId();
state.builder.AddFunction({spv::OpPhi, type, result, ConstantDeviceAddress(state, 0),
fault_label, available, available_label});
state.builder.AddFunction({spv::OpReturnValue, result});
state.builder.AddFunction({spv::OpFunctionEnd});
state.builder.AddFunction(spv::OpPhi, type, result, ConstantDeviceAddress(state, 0),
fault_label, available, available_label);
state.builder.AddFunction(spv::OpReturnValue, result);
state.builder.AddFunction(spv::OpFunctionEnd);
}
uint32_t EmitAtomic32(ValueEmitContext& ctx, const IR::Inst& inst) {
@@ -905,8 +905,8 @@ uint32_t EmitAtomic32(ValueEmitContext& ctx, const IR::Inst& inst) {
if (mem.kind == IR::ResourceKind::Lds) {
const auto semantics =
spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsWorkgroupMemoryMask;
ctx.state.builder.AddFunction({spv::OpMemoryBarrier, ConstantU32(ctx.state, scope),
ConstantU32(ctx.state, semantics)});
ctx.state.builder.AddFunction(spv::OpMemoryBarrier, ConstantU32(ctx.state, scope),
ConstantU32(ctx.state, semantics));
} else {
EmitDeviceAtomicMemoryBarrier(ctx.state);
}
@@ -932,11 +932,11 @@ uint32_t EmitBufferAtomic64(ValueEmitContext& ctx, const IR::Inst& inst) {
ctx.Arg(inst, inst.NumArgs() - 2));
const auto old = state.builder.AllocateId();
state.builder.AddFunction(
{SpirvAtomicOpcode(inst.GetOpcode()), TypeScalarU64(state), old,
EmitStorageBufferElementPointer(state, resource, index,
TypeStorageBufferU64ElementPointer(state)),
ConstantU32(state, spv::ScopeDevice),
ConstantU32(state, spv::MemorySemanticsMaskNone), value});
SpirvAtomicOpcode(inst.GetOpcode()), TypeScalarU64(state), old,
EmitStorageBufferElementPointer(state, resource, index,
TypeStorageBufferU64ElementPointer(state)),
ConstantU32(state, spv::ScopeDevice),
ConstantU32(state, spv::MemorySemanticsMaskNone), value);
EmitDeviceAtomicMemoryBarrier(state);
return Unary(state, spv::OpBitcast, TypeU64(state), old);
});
@@ -959,11 +959,11 @@ void EmitSharedFloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto access = PrepareMemoryElement(ctx, mem, DwordIndex(ctx, inst, mem));
EmitIfCondition(
ctx.state, EmitMemoryElementInBounds(ctx.state, access.resource, access.index), [&]() {
ctx.state.builder.AddFunction(
{spv::OpStore, ctx.scratch_u32_variable, ctx.Arg(inst, 1)});
ctx.state.builder.AddFunction(spv::OpStore, ctx.scratch_u32_variable,
ctx.Arg(inst, 1));
const auto data = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{spv::OpLoad, TypeU32(ctx.state), data, ctx.scratch_u32_variable});
ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), data,
ctx.scratch_u32_variable);
AtomicUpdate(
ctx.state, EmitMemoryElementPointer(ctx.state, access.resource, access.index),
mem.kind, [&](uint32_t old) {
@@ -1006,8 +1006,8 @@ uint32_t EmitAppendConsume(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto ballot = ctx.Ballot(inst.Arg(1));
const auto low = state.builder.AllocateId();
const auto high = state.builder.AllocateId();
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), low, ballot, 0});
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), high, ballot, 1});
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), low, ballot, 0);
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), high, ballot, 1);
const auto count = Binary(state, spv::OpIAdd, TypeU32(state),
Unary(state, spv::OpBitCount, TypeU32(state), low),
Unary(state, spv::OpBitCount, TypeU32(state), high));
@@ -1033,17 +1033,17 @@ uint32_t EmitAppendConsume(ValueEmitContext& ctx, const IR::Inst& inst) {
AndCondition(state, storage_bounds, m0_bounds)));
const auto atomic = EmitValueOrZeroIfCondition(state, condition, [&]() {
const auto value = state.builder.AllocateId();
state.builder.AddFunction(
{append ? spv::OpAtomicIAdd : spv::OpAtomicISub, TypeU32(state), value,
EmitMemoryElementPointer(state, access, index),
ConstantU32(state, mem.kind == IR::ResourceKind::Gds ? spv::ScopeDevice
: spv::ScopeWorkgroup),
ConstantU32(state, spv::MemorySemanticsMaskNone), count});
state.builder.AddFunction(append ? spv::OpAtomicIAdd : spv::OpAtomicISub, TypeU32(state),
value, EmitMemoryElementPointer(state, access, index),
ConstantU32(state, mem.kind == IR::ResourceKind::Gds
? spv::ScopeDevice
: spv::ScopeWorkgroup),
ConstantU32(state, spv::MemorySemanticsMaskNone), count);
return value;
});
const auto result = state.builder.AllocateId();
state.builder.AddFunction({spv::OpGroupNonUniformShuffle, TypeU32(state), result,
ConstantU32(state, spv::ScopeSubgroup), atomic, source_lane});
state.builder.AddFunction(spv::OpGroupNonUniformShuffle, TypeU32(state), result,
ConstantU32(state, spv::ScopeSubgroup), atomic, source_lane);
return result;
}
@@ -1053,9 +1053,9 @@ uint32_t EmitReadConst(ValueEmitContext& ctx, const IR::Inst& inst) {
ctx.Fail(inst, "requires the flattened SRT descriptor");
}
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
state.flattened_srt_variable, ConstantU32(state, 0),
ctx.Arg(inst, 1)});
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
state.flattened_srt_variable, ConstantU32(state, 0),
ctx.Arg(inst, 1));
return EmitNative<spv::OpLoad, IR::Type::U32>(state, pointer);
}
@@ -1131,7 +1131,7 @@ uint32_t EmitSharedIncDec(ValueEmitContext& ctx, const IR::Inst& inst) {
uint32_t EmitSwizzleU32(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
state.builder.AddFunction({spv::OpStore, ctx.scratch_u32_variable, ctx.Arg(inst, 0)});
state.builder.AddFunction(spv::OpStore, ctx.scratch_u32_variable, ctx.Arg(inst, 0));
const auto source = EmitNative<spv::OpLoad, IR::Type::U32>(state, ctx.scratch_u32_variable);
const auto target = EmitDsSwizzleTargetLane(state, EmitSubgroupLocalInvocationId(state),
inst.Arg(1).IsImmediate() ? inst.Arg(1).U32() : 0);
@@ -7,27 +7,27 @@ uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index) {
dword_index += state.program.bindings.push_data_start_dword;
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain, TypePushConstantElementPointer(state),
pointer, state.push_constant_variable, ConstantU32(state, 0),
ConstantU32(state, dword_index)});
state.builder.AddFunction({spv::OpLoad, TypeU32(state), value, pointer});
state.builder.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state),
pointer, state.push_constant_variable, ConstantU32(state, 0),
ConstantU32(state, dword_index));
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
return value;
}
if (state.shader_data_storage_variable != 0) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain, TypeStorageBufferElementPointer(state),
pointer, state.shader_data_storage_variable,
ConstantU32(state, 0), ConstantU32(state, dword_index)});
state.builder.AddFunction({spv::OpLoad, TypeU32(state), value, pointer});
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state),
pointer, state.shader_data_storage_variable,
ConstantU32(state, 0), ConstantU32(state, dword_index));
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
return value;
}
return ConstantU32(state, 0);
}
uint32_t EmitBinaryU32(EmitterState& state, uint32_t opcode, uint32_t lhs, uint32_t rhs) {
uint32_t EmitBinaryU32(EmitterState& state, spv::Op opcode, uint32_t lhs, uint32_t rhs) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({opcode, TypeU32(state), ret, lhs, rhs});
state.builder.AddFunction(opcode, TypeU32(state), ret, lhs, rhs);
return ret;
}
@@ -88,12 +88,12 @@ MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
ResourceForDescriptor(state, IR::DescriptorBindingKind::Buffers, mem.resource);
MemoryResourceAccess access {.kind = mem.kind};
access.object_pointer = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain, pointer_type, access.object_pointer, variable,
ConstantU32(state, array_index)});
state.builder.AddFunction(spv::OpAccessChain, pointer_type, access.object_pointer, variable,
ConstantU32(state, array_index));
access.byte_offset = state.memory_byte_offsets[array_index];
access.length = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpArrayLength, TypeU32(state), access.length, access.object_pointer, 0});
state.builder.AddFunction(spv::OpArrayLength, TypeU32(state), access.length,
access.object_pointer, 0);
return access;
}
@@ -139,8 +139,8 @@ MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::
default: EXIT("unsupported memory resource kind: %u\n", static_cast<unsigned>(mem.kind));
}
access.length = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpArrayLength, TypeU32(state), access.length, access.object_pointer, 0});
state.builder.AddFunction(spv::OpArrayLength, TypeU32(state), access.length,
access.object_pointer, 0);
return access;
}
@@ -152,7 +152,7 @@ uint32_t EmitMemoryElementIndex(EmitterState& state, const MemoryResourceAccess&
uint32_t EmitMemoryElementInBounds(EmitterState& state, const MemoryResourceAccess& access,
uint32_t index) {
const auto in_bounds = state.builder.AllocateId();
state.builder.AddFunction({spv::OpULessThan, TypeBool(state), in_bounds, index, access.length});
state.builder.AddFunction(spv::OpULessThan, TypeBool(state), in_bounds, index, access.length);
return in_bounds;
}
@@ -165,8 +165,8 @@ uint32_t EmitMemoryElementPointer(EmitterState& state, const MemoryResourceAcces
: ShaderWorkgroupInput(state.program.stage, state.input_info) != nullptr
? spv::StorageClassWorkgroup
: spv::StorageClassFunction;
state.builder.AddFunction({spv::OpAccessChain, TypeU32ElementPointer(state, storage_class),
pointer, access.object_pointer, index});
state.builder.AddFunction(spv::OpAccessChain, TypeU32ElementPointer(state, storage_class),
pointer, access.object_pointer, index);
return pointer;
}
return EmitStorageBufferElementPointer(state, access, index,
@@ -177,14 +177,14 @@ uint32_t EmitStorageBufferElementPointer(EmitterState& state,
const MemoryResourceAccess& access, uint32_t index,
uint32_t pointer_type) {
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain, pointer_type, pointer, access.object_pointer,
ConstantU32(state, 0), index});
state.builder.AddFunction(spv::OpAccessChain, pointer_type, pointer, access.object_pointer,
ConstantU32(state, 0), index);
return pointer;
}
uint32_t EmitTBufferBitcastU32ToI32(EmitterState& state, uint32_t value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({spv::OpBitcast, TypeI32(state), ret, value});
state.builder.AddFunction(spv::OpBitcast, TypeI32(state), ret, value);
return ret;
}
@@ -218,10 +218,10 @@ uint32_t EmitUFloatToF32Bits(EmitterState& state, uint32_t raw, uint32_t bits) {
const auto mantissa_f32 = state.builder.AllocateId();
const auto subnormal = state.builder.AllocateId();
state.builder.AddFunction({spv::OpConvertUToF, TypeF32(state), mantissa_f32, mantissa});
state.builder.AddFunction(spv::OpConvertUToF, TypeF32(state), mantissa_f32, mantissa);
state.builder.AddFunction(
{spv::OpFMul, TypeF32(state), subnormal, mantissa_f32,
ConstantF32Value(state, std::ldexp(1.0f, 1 - 15 - static_cast<int>(mantissa_bits)))});
spv::OpFMul, TypeF32(state), subnormal, mantissa_f32,
ConstantF32Value(state, std::ldexp(1.0f, 1 - 15 - static_cast<int>(mantissa_bits))));
const auto zero_exp = EmitCompareU32Constant(state, spv::OpIEqual, exponent, 0);
const auto special_exp = EmitCompareU32Constant(state, spv::OpIEqual, exponent, 31);
@@ -238,22 +238,22 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
case Format::ComponentType::Sint: return raw;
case Format::ComponentType::Uscaled: {
const auto value = state.builder.AllocateId();
state.builder.AddFunction({spv::OpConvertUToF, TypeF32(state), value, raw});
state.builder.AddFunction(spv::OpConvertUToF, TypeF32(state), value, raw);
return EmitBitcastF32ToU32(state, value);
}
case Format::ComponentType::Sscaled: {
const auto signed_raw = EmitTBufferBitcastU32ToI32(state, raw);
const auto value = state.builder.AllocateId();
state.builder.AddFunction({spv::OpConvertSToF, TypeF32(state), value, signed_raw});
state.builder.AddFunction(spv::OpConvertSToF, TypeF32(state), value, signed_raw);
return EmitBitcastF32ToU32(state, value);
}
case Format::ComponentType::Unorm: {
const auto value = state.builder.AllocateId();
const auto normalized = state.builder.AllocateId();
const auto max_value = static_cast<float>((1u << bits) - 1u);
state.builder.AddFunction({spv::OpConvertUToF, TypeF32(state), value, raw});
state.builder.AddFunction({spv::OpFDiv, TypeF32(state), normalized, value,
ConstantF32Value(state, max_value)});
state.builder.AddFunction(spv::OpConvertUToF, TypeF32(state), value, raw);
state.builder.AddFunction(spv::OpFDiv, TypeF32(state), normalized, value,
ConstantF32Value(state, max_value));
return EmitBitcastF32ToU32(state, normalized);
}
case Format::ComponentType::Snorm: {
@@ -262,11 +262,11 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
const auto normalized = state.builder.AllocateId();
const auto clamped = state.builder.AllocateId();
const auto max_value = static_cast<float>((1u << (bits - 1u)) - 1u);
state.builder.AddFunction({spv::OpConvertSToF, TypeF32(state), value, signed_raw});
state.builder.AddFunction({spv::OpFDiv, TypeF32(state), normalized, value,
ConstantF32Value(state, max_value)});
state.builder.AddFunction({spv::OpExtInst, TypeF32(state), clamped, GlslStd450(state),
GLSLstd450FMax, normalized, ConstantF32Value(state, -1.0f)});
state.builder.AddFunction(spv::OpConvertSToF, TypeF32(state), value, signed_raw);
state.builder.AddFunction(spv::OpFDiv, TypeF32(state), normalized, value,
ConstantF32Value(state, max_value));
state.builder.AddFunction(spv::OpExtInst, TypeF32(state), clamped, GlslStd450(state),
GLSLstd450FMax, normalized, ConstantF32Value(state, -1.0f));
return EmitBitcastF32ToU32(state, clamped);
}
case Format::ComponentType::Float:
@@ -284,8 +284,8 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
void EmitDeviceAtomicMemoryBarrier(EmitterState& state) {
const auto semantics =
spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsUniformMemoryMask;
state.builder.AddFunction({spv::OpMemoryBarrier, ConstantU32(state, spv::ScopeDevice),
ConstantU32(state, semantics)});
state.builder.AddFunction(spv::OpMemoryBarrier, ConstantU32(state, spv::ScopeDevice),
ConstantU32(state, semantics));
}
uint32_t EmitFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t source,
@@ -300,7 +300,7 @@ uint32_t EmitFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t
const auto negative = EmitCompareU32Constant(state, spv::OpINotEqual,
EmitAndConstant(state, bits, 0x80000000u), 0u);
const auto negative_key = state.builder.AllocateId();
state.builder.AddFunction({spv::OpNot, TypeU32(state), negative_key, bits});
state.builder.AddFunction(spv::OpNot, TypeU32(state), negative_key, bits);
const auto positive_key =
EmitBinaryU32(state, spv::OpBitwiseXor, bits, ConstantU32(state, 0x80000000u));
return OrderedBits {
@@ -313,8 +313,8 @@ uint32_t EmitFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t
state, EmitLogicalOrBool(state, source_class.nan, old_class.nan),
EmitLogicalAndBool(state, source_class.zero, old_class.zero));
const auto compare = state.builder.AllocateId();
state.builder.AddFunction({max_value ? spv::OpUGreaterThan : spv::OpULessThan, TypeBool(state),
compare, source_class.key, old_class.key});
state.builder.AddFunction(max_value ? spv::OpUGreaterThan : spv::OpULessThan, TypeBool(state),
compare, source_class.key, old_class.key);
return EmitSelectValueU32(
state, EmitLogicalAndBool(state, EmitLogicalNotBool(state, unordered), compare), source,
old);
@@ -342,17 +342,17 @@ uint32_t EmitDsSwizzleTargetLane(EmitterState& state, uint32_t subid, uint32_t c
const auto perm = state.builder.AllocateId();
const auto base = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), lane2, subid, ConstantU32(state, 3)});
state.builder.AddFunction(
{spv::OpShiftLeftLogical, TypeU32(state), shift, lane2, ConstantU32(state, 1)});
state.builder.AddFunction(
{spv::OpShiftRightLogical, TypeU32(state), perm0, ConstantU32(state, control), shift});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), perm, perm0, ConstantU32(state, 3)});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), base, subid, ConstantU32(state, 0xfffffffcu)});
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), target, base, perm});
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane2, subid,
ConstantU32(state, 3));
state.builder.AddFunction(spv::OpShiftLeftLogical, TypeU32(state), shift, lane2,
ConstantU32(state, 1));
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), perm0,
ConstantU32(state, control), shift);
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), perm, perm0,
ConstantU32(state, 3));
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), base, subid,
ConstantU32(state, 0xfffffffcu));
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, base, perm);
return target;
}
@@ -362,17 +362,17 @@ uint32_t EmitDsSwizzleTargetLane(EmitterState& state, uint32_t subid, uint32_t c
const auto xored = state.builder.AllocateId();
const auto base = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), lane, subid, ConstantU32(state, 31)});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), masked, lane, ConstantU32(state, control & 0x1fu)});
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), ored, masked,
ConstantU32(state, (control >> 5u) & 0x1fu)});
state.builder.AddFunction({spv::OpBitwiseXor, TypeU32(state), xored, ored,
ConstantU32(state, (control >> 10u) & 0x1fu)});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), base, subid, ConstantU32(state, 0xffffffe0u)});
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), target, base, xored});
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
ConstantU32(state, 31));
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), masked, lane,
ConstantU32(state, control & 0x1fu));
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), ored, masked,
ConstantU32(state, (control >> 5u) & 0x1fu));
state.builder.AddFunction(spv::OpBitwiseXor, TypeU32(state), xored, ored,
ConstantU32(state, (control >> 10u) & 0x1fu));
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), base, subid,
ConstantU32(state, 0xffffffe0u));
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, base, xored);
return target;
}
@@ -4,15 +4,15 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace {
uint32_t MeshArray(EmitterState& state, spv::StorageClass storage, uint32_t type, uint32_t count) {
const auto array = state.builder.Type(spv::OpTypeArray, {type, ConstantU32(state, count)});
const auto array = state.builder.Type(spv::OpTypeArray, type, ConstantU32(state, count));
return state.builder.DefineGlobalVariable(TypePointer(state, storage, array), storage);
}
uint32_t MeshElement(EmitterState& state, uint32_t variable, spv::StorageClass storage,
uint32_t type, uint32_t index) {
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpAccessChain, TypePointer(state, storage, type), pointer, variable, index});
state.builder.AddFunction(spv::OpAccessChain, TypePointer(state, storage, type), pointer,
variable, index);
return pointer;
}
@@ -20,7 +20,7 @@ uint32_t MeshLoad(EmitterState& state, uint32_t variable, spv::StorageClass stor
uint32_t index) {
const auto pointer = MeshElement(state, variable, storage, type, index);
const auto value = state.builder.AllocateId();
state.builder.AddFunction({spv::OpLoad, type, value, pointer});
state.builder.AddFunction(spv::OpLoad, type, value, pointer);
return value;
}
@@ -50,17 +50,17 @@ void DefineMeshOutputs(EmitterState& state) {
state.interface_variables.push_back(output.variable_id);
state.builder.AddName(output.variable_id, output.debug_name.c_str());
if (output.kind == IR::StageOutputKind::Parameter) {
state.builder.AddAnnotation(
{spv::OpDecorate, output.variable_id, spv::DecorationLocation, output.location});
state.builder.AddAnnotation(spv::OpDecorate, output.variable_id,
spv::DecorationLocation, output.location);
} else {
state.builder.AddAnnotation(
{spv::OpDecorate, output.variable_id, spv::DecorationBuiltIn,
output.kind == IR::StageOutputKind::Layer ? spv::BuiltInLayer
: spv::BuiltInPosition});
state.builder.AddAnnotation(spv::OpDecorate, output.variable_id, spv::DecorationBuiltIn,
output.kind == IR::StageOutputKind::Layer
? spv::BuiltInLayer
: spv::BuiltInPosition);
}
if (output.kind == IR::StageOutputKind::Layer) {
state.builder.AddAnnotation({spv::OpDecorate, output.variable_id,
spv::DecorationPerPrimitiveEXT}); // PerPrimitiveEXT
state.builder.AddAnnotation(spv::OpDecorate, output.variable_id,
spv::DecorationPerPrimitiveEXT); // PerPrimitiveEXT
}
}
state.mesh_allocation = MeshArray(state, spv::StorageClassWorkgroup, TypeU32(state), 2);
@@ -73,11 +73,11 @@ void DefineMeshOutputs(EmitterState& state) {
state.interface_variables.push_back(state.mesh_primitives);
state.interface_variables.push_back(state.mesh_cull);
state.builder.AddAnnotation(
{spv::OpDecorate, state.mesh_primitives, spv::DecorationBuiltIn,
spv::BuiltInPrimitiveTriangleIndicesEXT}); // PrimitiveTriangleIndicesEXT
state.builder.AddAnnotation({spv::OpDecorate, state.mesh_cull, spv::DecorationBuiltIn,
spv::BuiltInCullPrimitiveEXT}); // CullPrimitiveEXT
state.builder.AddAnnotation({spv::OpDecorate, state.mesh_cull, spv::DecorationPerPrimitiveEXT});
spv::OpDecorate, state.mesh_primitives, spv::DecorationBuiltIn,
spv::BuiltInPrimitiveTriangleIndicesEXT); // PrimitiveTriangleIndicesEXT
state.builder.AddAnnotation(spv::OpDecorate, state.mesh_cull, spv::DecorationBuiltIn,
spv::BuiltInCullPrimitiveEXT); // CullPrimitiveEXT
state.builder.AddAnnotation(spv::OpDecorate, state.mesh_cull, spv::DecorationPerPrimitiveEXT);
}
uint32_t MeshOutputPointer(EmitterState& state, IR::StageOutputKind kind, uint32_t index) {
@@ -103,45 +103,45 @@ uint32_t MeshPrimitivePointer(EmitterState& state) {
void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto first = state.builder.AllocateId();
state.builder.AddFunction({spv::OpIEqual, TypeBool(state), first,
EmitLocalInvocationIndex(state), ConstantU32(state, 0)});
state.builder.AddFunction(spv::OpIEqual, TypeBool(state), first,
EmitLocalInvocationIndex(state), ConstantU32(state, 0));
EmitIfCondition(state, first, [&] {
const auto allocation = ctx.Arg(inst, 0);
for (uint32_t field = 0; field < 2; field++) {
const auto value = state.builder.AllocateId();
state.builder.AddFunction({spv::OpBitFieldUExtract, TypeU32(state), value, allocation,
ConstantU32(state, field * 12u),
ConstantU32(state, field == 0 ? 10u : 11u)});
state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), value, allocation,
ConstantU32(state, field * 12u),
ConstantU32(state, field == 0 ? 10u : 11u));
const auto pointer =
MeshElement(state, state.mesh_allocation, spv::StorageClassWorkgroup,
TypeU32(state), ConstantU32(state, field));
state.builder.AddFunction({spv::OpStore, pointer, value});
state.builder.AddFunction(spv::OpStore, pointer, value);
}
});
}
void EmitMeshEntryPoint(EmitterState& state) {
state.builder.AddFunction({spv::OpFunction, TypeVoid(state), state.main_func,
spv::FunctionControlMaskNone, TypeFunction(state)});
state.builder.AddFunction(spv::OpFunction, TypeVoid(state), state.main_func,
spv::FunctionControlMaskNone, TypeFunction(state));
EmitLabel(state, state.builder.AllocateId());
state.builder.AddFunction(
{spv::OpFunctionCall, TypeVoid(state), state.builder.AllocateId(), state.mesh_guest_func});
state.builder.AddFunction(spv::OpFunctionCall, TypeVoid(state), state.builder.AllocateId(),
state.mesh_guest_func);
// All guest waves finish before the uniform Vulkan allocation and output stores.
state.builder.AddFunction({spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask |
spv::MemorySemanticsWorkgroupMemoryMask)});
state.builder.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask |
spv::MemorySemanticsWorkgroupMemoryMask));
const auto vertices = MeshLoad(state, state.mesh_allocation, spv::StorageClassWorkgroup,
TypeU32(state), ConstantU32(state, 0));
const auto primitives = MeshLoad(state, state.mesh_allocation, spv::StorageClassWorkgroup,
TypeU32(state), ConstantU32(state, 1));
state.builder.AddFunction(
{spv::OpSetMeshOutputsEXT, vertices, primitives}); // OpSetMeshOutputsEXT
state.builder.AddFunction(spv::OpSetMeshOutputsEXT, vertices,
primitives); // OpSetMeshOutputsEXT
for (uint32_t half = 0; half < state.lane_count; half++) {
state.lane_half = half;
const auto index = EmitLocalInvocationIndex(state);
const auto is_vertex = state.builder.AllocateId();
state.builder.AddFunction({spv::OpULessThan, TypeBool(state), is_vertex, index, vertices});
state.builder.AddFunction(spv::OpULessThan, TypeBool(state), is_vertex, index, vertices);
EmitIfCondition(state, is_vertex, [&] {
for (const auto& output: state.outputs) {
if (output.kind == IR::StageOutputKind::Layer) {
@@ -153,12 +153,12 @@ void EmitMeshEntryPoint(EmitterState& state) {
ConstantU32(state, half));
const auto pointer =
MeshElement(state, output.variable_id, spv::StorageClassOutput, type, index);
state.builder.AddFunction({spv::OpStore, pointer, value});
state.builder.AddFunction(spv::OpStore, pointer, value);
}
});
const auto is_primitive = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpULessThan, TypeBool(state), is_primitive, index, primitives});
state.builder.AddFunction(spv::OpULessThan, TypeBool(state), is_primitive, index,
primitives);
EmitIfCondition(state, is_primitive, [&] {
const auto packed = MeshLoad(state, state.mesh_primitive_data, spv::StorageClassPrivate,
TypeU32(state), ConstantU32(state, half));
@@ -166,25 +166,25 @@ void EmitMeshEntryPoint(EmitterState& state) {
for (uint32_t component = 0; component < 3; component++) {
vertex[component] = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpBitFieldUExtract, TypeU32(state), vertex[component], packed,
ConstantU32(state, component * 10u), ConstantU32(state, 10)});
spv::OpBitFieldUExtract, TypeU32(state), vertex[component], packed,
ConstantU32(state, component * 10u), ConstantU32(state, 10));
}
const auto triangle = state.builder.AllocateId();
state.builder.AddFunction({spv::OpCompositeConstruct, TypeU32Vector(state, 3), triangle,
vertex[0], vertex[1], vertex[2]});
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 3), triangle,
vertex[0], vertex[1], vertex[2]);
const auto triangle_pointer =
MeshElement(state, state.mesh_primitives, spv::StorageClassOutput,
TypeU32Vector(state, 3), index);
state.builder.AddFunction({spv::OpStore, triangle_pointer, triangle});
state.builder.AddFunction(spv::OpStore, triangle_pointer, triangle);
const auto null_bit =
EmitBinaryU32(state, spv::OpBitwiseAnd, packed, ConstantU32(state, 0x80000000u));
const auto culled = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpINotEqual, TypeBool(state), culled, null_bit, ConstantU32(state, 0)});
state.builder.AddFunction({spv::OpStore,
MeshElement(state, state.mesh_cull, spv::StorageClassOutput,
TypeBool(state), index),
culled});
state.builder.AddFunction(spv::OpINotEqual, TypeBool(state), culled, null_bit,
ConstantU32(state, 0));
state.builder.AddFunction(spv::OpStore,
MeshElement(state, state.mesh_cull, spv::StorageClassOutput,
TypeBool(state), index),
culled);
for (const auto& output: state.outputs) {
if (output.kind != IR::StageOutputKind::Layer) {
continue;
@@ -194,13 +194,13 @@ void EmitMeshEntryPoint(EmitterState& state) {
vertex[state.input_info.vertex->mesh.provoking_vertex]);
const auto pointer = MeshElement(state, output.variable_id, spv::StorageClassOutput,
TypeU32(state), index);
state.builder.AddFunction({spv::OpStore, pointer, layer});
state.builder.AddFunction(spv::OpStore, pointer, layer);
}
});
}
state.lane_half = 0;
state.builder.AddFunction({spv::OpReturn});
state.builder.AddFunction({spv::OpFunctionEnd});
state.builder.AddFunction(spv::OpReturn);
state.builder.AddFunction(spv::OpFunctionEnd);
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -13,11 +13,11 @@ uint32_t TypeBool(EmitterState& state) {
}
uint32_t TypeBoolVector(EmitterState& state, uint32_t components) {
return state.builder.Type(spv::OpTypeVector, {TypeBool(state), components});
return state.builder.Type(spv::OpTypeVector, TypeBool(state), components);
}
uint32_t TypeU32(EmitterState& state) {
return state.builder.Type(spv::OpTypeInt, {32, 0});
return state.builder.Type(spv::OpTypeInt, 32, 0);
}
uint32_t TypeU64(EmitterState& state) {
@@ -25,29 +25,29 @@ uint32_t TypeU64(EmitterState& state) {
}
uint32_t TypeScalarU64(EmitterState& state) {
return state.builder.Type(spv::OpTypeInt, {64, 0});
return state.builder.Type(spv::OpTypeInt, 64, 0);
}
uint32_t TypeU32Pair(EmitterState& state) {
const auto element = TypeU32(state);
return state.builder.Type(spv::OpTypeStruct, {element, element});
return state.builder.Type(spv::OpTypeStruct, element, element);
}
uint32_t TypeI32(EmitterState& state) {
return state.builder.Type(spv::OpTypeInt, {32, 1});
return state.builder.Type(spv::OpTypeInt, 32, 1);
}
uint32_t TypeI32Pair(EmitterState& state) {
const auto element = TypeI32(state);
return state.builder.Type(spv::OpTypeStruct, {element, element});
return state.builder.Type(spv::OpTypeStruct, element, element);
}
uint32_t TypeF32(EmitterState& state) {
return state.builder.Type(spv::OpTypeFloat, {32});
return state.builder.Type(spv::OpTypeFloat, 32);
}
uint32_t TypeU32Vector(EmitterState& state, uint32_t components) {
return state.builder.Type(spv::OpTypeVector, {TypeU32(state), components});
return state.builder.Type(spv::OpTypeVector, TypeU32(state), components);
}
uint32_t TypeU32Composite(EmitterState& state, uint32_t components) {
@@ -56,31 +56,32 @@ uint32_t TypeU32Composite(EmitterState& state, uint32_t components) {
}
uint32_t TypeI32Vector(EmitterState& state, uint32_t components) {
return state.builder.Type(spv::OpTypeVector, {TypeI32(state), components});
return state.builder.Type(spv::OpTypeVector, TypeI32(state), components);
}
uint32_t TypeF32Vector(EmitterState& state, uint32_t components) {
return state.builder.Type(spv::OpTypeVector, {TypeF32(state), components});
return state.builder.Type(spv::OpTypeVector, TypeF32(state), components);
}
uint32_t TypePointer(EmitterState& state, spv::StorageClass storage_class, uint32_t pointee) {
return state.builder.Type(spv::OpTypePointer, {storage_class, pointee});
return state.builder.Type(spv::OpTypePointer, storage_class, pointee);
}
uint32_t TypeFunction(EmitterState& state) {
return state.builder.Type(spv::OpTypeFunction, {TypeVoid(state)});
return state.builder.Type(spv::OpTypeFunction, TypeVoid(state));
}
uint32_t StorageRuntimeArrayType(EmitterState& state) {
return state.builder.DecoratedType(
spv::OpTypeRuntimeArray, {TypeU32(state)},
{{spv::OpDecorate, {spv::DecorationArrayStride, sizeof(uint32_t)}}});
spv::OpTypeRuntimeArray,
{{spv::OpDecorate, {spv::DecorationArrayStride, sizeof(uint32_t)}}}, TypeU32(state));
}
uint32_t StorageBufferType(EmitterState& state) {
return state.builder.DecoratedType(spv::OpTypeStruct, {StorageRuntimeArrayType(state)},
return state.builder.DecoratedType(spv::OpTypeStruct,
{{spv::OpMemberDecorate, {0, spv::DecorationOffset, 0}},
{spv::OpDecorate, {spv::DecorationBlock}}});
{spv::OpDecorate, {spv::DecorationBlock}}},
StorageRuntimeArrayType(state));
}
uint32_t TypeStorageBufferPointer(EmitterState& state) {
@@ -93,14 +94,15 @@ uint32_t TypeStorageBufferElementPointer(EmitterState& state) {
uint32_t StorageU64RuntimeArrayType(EmitterState& state) {
return state.builder.DecoratedType(
spv::OpTypeRuntimeArray, {TypeScalarU64(state)},
{{spv::OpDecorate, {spv::DecorationArrayStride, sizeof(uint64_t)}}});
spv::OpTypeRuntimeArray,
{{spv::OpDecorate, {spv::DecorationArrayStride, sizeof(uint64_t)}}}, TypeScalarU64(state));
}
uint32_t StorageBufferU64Type(EmitterState& state) {
return state.builder.DecoratedType(spv::OpTypeStruct, {StorageU64RuntimeArrayType(state)},
return state.builder.DecoratedType(spv::OpTypeStruct,
{{spv::OpMemberDecorate, {0, spv::DecorationOffset, 0}},
{spv::OpDecorate, {spv::DecorationBlock}}});
{spv::OpDecorate, {spv::DecorationBlock}}},
StorageU64RuntimeArrayType(state));
}
uint32_t TypeStorageBufferU64Pointer(EmitterState& state) {
@@ -122,7 +124,7 @@ uint32_t TypePushConstantElementPointer(EmitterState& state) {
uint32_t TypeU32ArrayPointer(EmitterState& state, spv::StorageClass storage_class,
uint32_t dwords) {
const auto count = ConstantU32(state, std::max(dwords, 1u));
const auto array = state.builder.Type(spv::OpTypeArray, {TypeU32(state), count});
const auto array = state.builder.Type(spv::OpTypeArray, TypeU32(state), count);
return TypePointer(state, storage_class, array);
}
@@ -135,29 +137,31 @@ namespace {
uint32_t PushConstantArrayType(EmitterState& state) {
const auto count = ConstantU32(state, IR::PushData::DwordCount);
return state.builder.DecoratedType(
spv::OpTypeArray, {TypeU32(state), count},
{{spv::OpDecorate, {spv::DecorationArrayStride, sizeof(uint32_t)}}});
spv::OpTypeArray, {{spv::OpDecorate, {spv::DecorationArrayStride, sizeof(uint32_t)}}},
TypeU32(state), count);
}
uint32_t PushConstantBlockType(EmitterState& state) {
return state.builder.DecoratedType(spv::OpTypeStruct, {PushConstantArrayType(state)},
return state.builder.DecoratedType(spv::OpTypeStruct,
{{spv::OpMemberDecorate, {0, spv::DecorationOffset, 0}},
{spv::OpDecorate, {spv::DecorationBlock}}});
{spv::OpDecorate, {spv::DecorationBlock}}},
PushConstantArrayType(state));
}
uint32_t PerVertexType(EmitterState& state) {
return state.builder.DecoratedType(
spv::OpTypeStruct, {TypeF32Vector(state, 4)},
spv::OpTypeStruct,
{{spv::OpMemberDecorate, {0, spv::DecorationBuiltIn, spv::BuiltInPosition}},
{spv::OpDecorate, {spv::DecorationBlock}}});
{spv::OpDecorate, {spv::DecorationBlock}}},
TypeF32Vector(state, 4));
}
uint32_t SampleMaskArrayType(EmitterState& state) {
return state.builder.Type(spv::OpTypeArray, {TypeI32(state), ConstantU32(state, 1)});
return state.builder.Type(spv::OpTypeArray, TypeI32(state), ConstantU32(state, 1));
}
uint32_t F32ArrayType(EmitterState& state, uint32_t count) {
return state.builder.Type(spv::OpTypeArray, {TypeF32(state), ConstantU32(state, count)});
return state.builder.Type(spv::OpTypeArray, TypeF32(state), ConstantU32(state, count));
}
void DefineDescriptors(EmitterState& state) {
@@ -174,16 +178,15 @@ void DefineDescriptors(EmitterState& state) {
const auto variable =
state.builder.DefineGlobalVariable(TypePointer(state, storage, type), storage);
state.builder.AddName(variable, name);
state.builder.AddAnnotation(
{spv::OpDecorate, variable, spv::DecorationDescriptorSet, 0});
state.builder.AddAnnotation({spv::OpDecorate, variable, spv::DecorationBinding,
IR::NativeBinding(state.program.stage, binding.kind)});
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationDescriptorSet, 0);
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBinding,
IR::NativeBinding(state.program.stage, binding.kind));
return variable;
};
const auto ArrayType = [&](uint32_t type) {
return state.builder.Type(
spv::OpTypeArray,
{type, ConstantU32(state, static_cast<uint32_t>(binding.resources.size()))});
spv::OpTypeArray, type,
ConstantU32(state, static_cast<uint32_t>(binding.resources.size())));
};
switch (binding.kind) {
case IR::DescriptorBindingKind::Buffers:
@@ -192,10 +195,10 @@ void DefineDescriptors(EmitterState& state) {
if (state.requirements.buffer_int64_atomics) {
state.storage_buffer_u64_variable =
Define(ArrayType(StorageBufferU64Type(state)), "buffers_u64");
state.builder.AddAnnotation(
{spv::OpDecorate, state.storage_buffer_variable, spv::DecorationAliased});
state.builder.AddAnnotation({spv::OpDecorate, state.storage_buffer_u64_variable,
spv::DecorationAliased});
state.builder.AddAnnotation(spv::OpDecorate, state.storage_buffer_variable,
spv::DecorationAliased);
state.builder.AddAnnotation(spv::OpDecorate, state.storage_buffer_u64_variable,
spv::DecorationAliased);
}
break;
case IR::DescriptorBindingKind::BdaPagetable:
@@ -242,15 +245,15 @@ void DefineDescriptors(EmitterState& state) {
} // namespace
uint32_t ConstantU32(EmitterState& state, uint32_t value) {
return state.builder.Constant(spv::OpConstant, TypeU32(state), {value});
return state.builder.Constant(spv::OpConstant, TypeU32(state), value);
}
uint32_t ConstantI32(EmitterState& state, int32_t value) {
return state.builder.Constant(spv::OpConstant, TypeI32(state), {static_cast<uint32_t>(value)});
return state.builder.Constant(spv::OpConstant, TypeI32(state), static_cast<uint32_t>(value));
}
uint32_t ConstantF32(EmitterState& state, uint32_t bits) {
return state.builder.Constant(spv::OpConstant, TypeF32(state), {bits});
return state.builder.Constant(spv::OpConstant, TypeF32(state), bits);
}
uint32_t FloatBits(float value) {
@@ -270,8 +273,8 @@ uint32_t ConstantBool(EmitterState& state, bool value) {
uint32_t ConstantU64(EmitterState& state, uint64_t value) {
return state.builder.Constant(spv::OpConstantComposite, TypeU64(state),
{ConstantU32(state, static_cast<uint32_t>(value)),
ConstantU32(state, static_cast<uint32_t>(value >> 32u))});
ConstantU32(state, static_cast<uint32_t>(value)),
ConstantU32(state, static_cast<uint32_t>(value >> 32u)));
}
uint32_t ConstantU32CompositeZero(EmitterState& state, uint32_t components) {
@@ -287,7 +290,8 @@ uint32_t GlslStd450(EmitterState& state) {
}
VertexInputScalarKind VertexParameterScalarKind(const EmitterState& state, uint32_t location) {
if (state.program.stage != ShaderType::Vertex || location >= ShaderVertexInputInfo::RES_MAX ||
if ((state.program.stage != ShaderType::Vertex && state.program.stage != ShaderType::Local) ||
location >= ShaderVertexInputInfo::RES_MAX ||
location >= static_cast<uint32_t>(state.input_info.vertex->resources_num)) {
return VertexInputScalarKind::Float;
}
@@ -330,8 +334,6 @@ uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind ki
}
}
namespace {
uint32_t DefineInterfaceVariable(EmitterState& state, uint32_t type, spv::StorageClass storage,
const char* name) {
const auto variable =
@@ -341,9 +343,14 @@ uint32_t DefineInterfaceVariable(EmitterState& state, uint32_t type, spv::Storag
return variable;
}
namespace {
uint32_t BuiltInForInput(IR::StageInputKind kind) {
switch (kind) {
case IR::StageInputKind::VertexIndex: return spv::BuiltInVertexIndex;
case IR::StageInputKind::InvocationId: return spv::BuiltInInvocationId;
case IR::StageInputKind::PrimitiveId: return spv::BuiltInPrimitiveId;
case IR::StageInputKind::TessCoord: return spv::BuiltInTessCoord;
case IR::StageInputKind::InstanceIndex: return spv::BuiltInInstanceIndex;
case IR::StageInputKind::FragCoord: return spv::BuiltInFragCoord;
case IR::StageInputKind::FrontFacing: return spv::BuiltInFrontFacing;
@@ -384,6 +391,8 @@ void DefineInputs(EmitterState& state) {
uint32_t type = TypeU32(state);
switch (input.kind) {
case IR::StageInputKind::VertexIndex:
case IR::StageInputKind::InvocationId:
case IR::StageInputKind::PrimitiveId:
case IR::StageInputKind::InstanceIndex:
case IR::StageInputKind::Layer:
case IR::StageInputKind::SampleId: type = TypeI32(state); break;
@@ -391,20 +400,22 @@ void DefineInputs(EmitterState& state) {
case IR::StageInputKind::LocalInvocationId:
case IR::StageInputKind::GlobalInvocationId: type = TypeU32Vector(state, 3); break;
case IR::StageInputKind::FragCoord: type = TypeF32Vector(state, 4); break;
case IR::StageInputKind::TessCoord:
case IR::StageInputKind::BaryCoordSmooth:
case IR::StageInputKind::BaryCoordNoPerspective: type = TypeF32Vector(state, 3); break;
case IR::StageInputKind::FrontFacing: type = TypeBool(state); break;
case IR::StageInputKind::Parameter:
if (state.program.stage == ShaderType::Vertex) {
if (state.program.stage == ShaderType::Vertex ||
state.program.stage == ShaderType::Local) {
type = VertexParameterScalarType(
state, VertexParameterScalarKind(state, input.location));
const auto components = VertexParameterComponentCount(input);
if (components > 1u) {
type = state.builder.Type(spv::OpTypeVector, {type, components});
type = state.builder.Type(spv::OpTypeVector, type, components);
}
} else if (input.per_vertex) {
type = state.builder.Type(spv::OpTypeArray,
{TypeF32Vector(state, 4), ConstantU32(state, 3)});
type = state.builder.Type(spv::OpTypeArray, TypeF32Vector(state, 4),
ConstantU32(state, 3));
} else {
type = TypeF32Vector(state, 4);
}
@@ -414,38 +425,37 @@ void DefineInputs(EmitterState& state) {
input.variable_id =
DefineInterfaceVariable(state, type, spv::StorageClassInput, input.debug_name.c_str());
if (input.kind == IR::StageInputKind::Layer || input.kind == IR::StageInputKind::SampleId) {
state.builder.AddAnnotation({spv::OpDecorate, input.variable_id, spv::DecorationFlat});
state.builder.AddAnnotation(spv::OpDecorate, input.variable_id, spv::DecorationFlat);
}
if (input.kind == IR::StageInputKind::Parameter) {
const auto flat = PixelParameterIsFlat(state, input.location);
if (input.per_vertex) {
state.builder.AddAnnotation(
{spv::OpDecorate, input.variable_id, spv::DecorationPerVertexKHR});
state.builder.AddAnnotation(spv::OpDecorate, input.variable_id,
spv::DecorationPerVertexKHR);
} else if (flat) {
state.builder.AddAnnotation(
{spv::OpDecorate, input.variable_id, spv::DecorationFlat});
state.builder.AddAnnotation(spv::OpDecorate, input.variable_id,
spv::DecorationFlat);
}
if (state.program.stage == ShaderType::Pixel &&
state.input_info.pixel->ps_no_perspective && !flat && !input.per_vertex) {
state.builder.AddAnnotation(
{spv::OpDecorate, input.variable_id, spv::DecorationNoPerspective});
state.builder.AddAnnotation(spv::OpDecorate, input.variable_id,
spv::DecorationNoPerspective);
}
state.builder.AddAnnotation({spv::OpDecorate, input.variable_id,
spv::DecorationLocation,
PixelParameterLocation(state, input.location)});
state.builder.AddAnnotation(spv::OpDecorate, input.variable_id, spv::DecorationLocation,
PixelParameterLocation(state, input.location));
} else if (const auto builtin = BuiltInForInput(input.kind); builtin != UINT32_MAX) {
state.builder.AddAnnotation(
{spv::OpDecorate, input.variable_id, spv::DecorationBuiltIn, builtin});
state.builder.AddAnnotation(spv::OpDecorate, input.variable_id, spv::DecorationBuiltIn,
builtin);
}
}
if (state.requirements.subgroup_local_invocation_id) {
const auto variable = DefineInterfaceVariable(state, TypeU32(state), spv::StorageClassInput,
"gl_SubgroupInvocationID");
state.subgroup_local_invocation_id_variable = variable;
state.builder.AddAnnotation({spv::OpDecorate, variable, spv::DecorationBuiltIn,
spv::BuiltInSubgroupLocalInvocationId});
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBuiltIn,
spv::BuiltInSubgroupLocalInvocationId);
if (state.program.stage == ShaderType::Pixel) {
state.builder.AddAnnotation({spv::OpDecorate, variable, spv::DecorationFlat});
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationFlat);
}
}
}
@@ -470,8 +480,7 @@ void DefineOutputs(EmitterState& state) {
spv::BuiltIn builtin) {
if (variable == 0) {
variable = DefineInterfaceVariable(state, type, spv::StorageClassOutput, name);
state.builder.AddAnnotation(
{spv::OpDecorate, variable, spv::DecorationBuiltIn, builtin});
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBuiltIn, builtin);
}
return variable;
};
@@ -527,8 +536,8 @@ void DefineOutputs(EmitterState& state) {
const auto type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
binding.variable_id = DefineInterfaceVariable(state, type, spv::StorageClassOutput,
binding.debug_name.c_str());
state.builder.AddAnnotation({spv::OpDecorate, binding.variable_id,
spv::DecorationLocation, binding.location});
state.builder.AddAnnotation(spv::OpDecorate, binding.variable_id,
spv::DecorationLocation, binding.location);
break;
}
}
@@ -542,6 +551,7 @@ void DefineModule(EmitterState& state) {
state.program.info.outputs.size());
DefineInputs(state);
DefineOutputs(state);
DefineTessellationInterfaces(state);
DefineDescriptors(state);
if (state.requirements.function_lds) {
state.lds_variable = state.builder.AllocateId();
@@ -556,12 +566,16 @@ void DefineModule(EmitterState& state) {
state.mesh_guest_func = state.builder.AllocateId();
state.builder.RequireCapability(spv::CapabilityMeshShadingEXT); // MeshShadingEXT
state.builder.RequireExtension("SPV_EXT_mesh_shader");
state.builder.AddExecutionMode(
{state.main_func, spv::ExecutionModeOutputTrianglesEXT}); // OutputTrianglesEXT
state.builder.AddExecutionMode({state.main_func, spv::ExecutionModeOutputVertices,
state.input_info.vertex->mesh.max_vertices});
state.builder.AddExecutionMode({state.main_func, spv::ExecutionModeOutputPrimitivesEXT,
state.input_info.vertex->mesh.max_primitives});
state.builder.AddExecutionMode(state.main_func,
spv::ExecutionModeOutputTrianglesEXT); // OutputTrianglesEXT
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputVertices,
state.input_info.vertex->mesh.max_vertices);
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputPrimitivesEXT,
state.input_info.vertex->mesh.max_primitives);
}
if (state.program.stage == ShaderType::TessellationControl ||
state.program.stage == ShaderType::TessellationEvaluation) {
DefineTessellationExecutionModes(state);
}
state.entry_label = state.builder.AllocateId();
@@ -621,14 +635,14 @@ void DefineModule(EmitterState& state) {
state.builder.RequireExtension("SPV_KHR_fragment_shader_barycentric");
}
state.builder.RequireExtension("SPV_KHR_float_controls");
state.builder.AddMemoryModel({state.program.info.uses_dma
? spv::AddressingModelPhysicalStorageBuffer64
: spv::AddressingModelLogical,
spv::MemoryModelGLSL450});
state.builder.AddMemoryModel(state.program.info.uses_dma
? spv::AddressingModelPhysicalStorageBuffer64
: spv::AddressingModelLogical,
spv::MemoryModelGLSL450);
// GCN/RDNA arithmetic preserves 32-bit signed zero, infinity, and NaN. Declaring that
// contract prevents host compilers from treating synthesized IEEE values as finite.
state.builder.AddExecutionMode(
{state.main_func, spv::ExecutionModeSignedZeroInfNanPreserve, 32u});
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeSignedZeroInfNanPreserve,
32u);
if (const auto* cs = ShaderWorkgroupInput(state.program.stage, state.input_info)) {
uint32_t local_x = state.requirements.compute_derivatives ? 2u : 1u;
uint32_t local_y = state.requirements.compute_derivatives ? 2u : 1u;
@@ -644,23 +658,22 @@ void DefineModule(EmitterState& state) {
local_x = 2u;
}
}
state.builder.AddExecutionMode(
{state.main_func, spv::ExecutionModeLocalSize, local_x, local_y, local_z});
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeLocalSize, local_x,
local_y, local_z);
}
if (state.program.stage == ShaderType::Pixel) {
state.builder.AddExecutionMode({state.main_func, spv::ExecutionModeOriginUpperLeft});
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOriginUpperLeft);
if (state.depth_variable != 0) {
state.builder.AddExecutionMode({state.main_func, spv::ExecutionModeDepthReplacing});
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeDepthReplacing);
}
if (state.input_info.pixel->ps_early_z && !state.input_info.pixel->ps_pixel_kill_enable &&
!state.input_info.pixel->ps_depth_export_enable &&
!state.input_info.pixel->ps_sample_mask_export_enable) {
state.builder.AddExecutionMode({state.main_func, spv::ExecutionModeEarlyFragmentTests});
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeEarlyFragmentTests);
}
}
if (state.requirements.compute_derivatives && state.program.stage == ShaderType::Compute) {
state.builder.AddExecutionMode(
{state.main_func, spv::ExecutionModeDerivativeGroupQuadsKHR});
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeDerivativeGroupQuadsKHR);
}
state.builder.AddName(state.main_func, "main");
if (state.requirements.function_lds) {
@@ -17,11 +17,11 @@ void EmitKillIfBoolFalse(EmitterState& state, uint32_t active) {
const auto kill_label = state.builder.AllocateId();
const auto merge_label = state.builder.AllocateId();
const auto inactive = state.builder.AllocateId();
state.builder.AddFunction({spv::OpLogicalNot, TypeBool(state), inactive, active});
state.builder.AddFunction({spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone});
state.builder.AddFunction({spv::OpBranchConditional, inactive, kill_label, merge_label});
state.builder.AddFunction(spv::OpLogicalNot, TypeBool(state), inactive, active);
state.builder.AddFunction(spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
state.builder.AddFunction(spv::OpBranchConditional, inactive, kill_label, merge_label);
EmitLabel(state, kill_label);
state.builder.AddFunction({spv::OpKill});
state.builder.AddFunction(spv::OpKill);
EmitLabel(state, merge_label);
}
@@ -32,10 +32,10 @@ void EmitKillIfPixelValidMaskInactive(EmitterState& state) {
const auto mask_value = state.builder.AllocateId();
const auto active = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpLoad, TypeU32(state), mask_value, state.pixel_valid_mask_variable});
state.builder.AddFunction(
{spv::OpINotEqual, TypeBool(state), active, mask_value, ConstantU32(state, 0)});
state.builder.AddFunction(spv::OpLoad, TypeU32(state), mask_value,
state.pixel_valid_mask_variable);
state.builder.AddFunction(spv::OpINotEqual, TypeBool(state), active, mask_value,
ConstantU32(state, 0));
EmitKillIfBoolFalse(state, active);
}
@@ -75,8 +75,8 @@ void StoreDispatcherPhiEdge(ValueEmitContext& ctx, const DispatcherFunctionState
}
for (size_t index = 0; index < phi.NumArgs(); index++) {
if (phi.PhiBlock(index) == from) {
ctx.state.builder.AddFunction(
{spv::OpStore, dispatcher.spills[ctx.half].at(&phi), ctx.Def(phi.Arg(index))});
ctx.state.builder.AddFunction(spv::OpStore, dispatcher.spills[ctx.half].at(&phi),
ctx.Def(phi.Arg(index)));
break;
}
}
@@ -94,7 +94,7 @@ const IR::Block* TargetBlock(const IR::Program& program, uint32_t id) {
void EmitReturn(ValueEmitContext& ctx) {
EmitKillIfPixelValidMaskInactive(ctx.state);
ctx.state.builder.AddFunction({spv::OpReturn});
ctx.state.builder.AddFunction(spv::OpReturn);
}
uint32_t BranchCondition(ValueEmitContext& ctx, const IR::BlockInfo& info) {
@@ -108,16 +108,16 @@ uint32_t BranchCondition(ValueEmitContext& ctx, const IR::BlockInfo& info) {
const auto low = ctx.state.builder.AllocateId();
const auto high = ctx.state.builder.AllocateId();
const auto result = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(ctx.state), low, ballot, 0});
ctx.state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(ctx.state), high, ballot, 1});
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), low, ballot, 0);
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), high, ballot, 1);
const auto kind = info.terminator.condition;
const bool zero = kind == CFG::BranchCondition::ExecZero ||
kind == CFG::BranchCondition::VccZero ||
kind == CFG::BranchCondition::SccZero;
const auto combined =
EmitBinaryU32(ctx.state, zero ? spv::OpBitwiseAnd : spv::OpBitwiseOr, low, high);
ctx.state.builder.AddFunction({zero ? spv::OpIEqual : spv::OpINotEqual, TypeBool(ctx.state),
result, combined, ConstantU32(ctx.state, zero ? ~0u : 0u)});
ctx.state.builder.AddFunction(zero ? spv::OpIEqual : spv::OpINotEqual, TypeBool(ctx.state),
result, combined, ConstantU32(ctx.state, zero ? ~0u : 0u));
return result;
}
@@ -130,14 +130,14 @@ void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
const auto* merge = TargetBlock(program, term.merge_block);
const auto* cont = TargetBlock(program, term.continue_block);
if (merge != nullptr && cont != nullptr) {
ctx.state.builder.AddFunction({spv::OpLoopMerge, ctx.Label(merge), ctx.Label(cont),
spv::LoopControlMaskNone});
ctx.state.builder.AddFunction(spv::OpLoopMerge, ctx.Label(merge), ctx.Label(cont),
spv::LoopControlMaskNone);
}
} else if (term.kind == CFG::TerminatorKind::ConditionalBranch &&
term.merge_block != UINT32_MAX) {
if (const auto* merge = TargetBlock(program, term.merge_block); merge != nullptr) {
ctx.state.builder.AddFunction(
{spv::OpSelectionMerge, ctx.Label(merge), spv::SelectionControlMaskNone});
ctx.state.builder.AddFunction(spv::OpSelectionMerge, ctx.Label(merge),
spv::SelectionControlMaskNone);
}
}
};
@@ -150,7 +150,7 @@ void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
return;
}
emit_merge();
ctx.state.builder.AddFunction({spv::OpBranch, ctx.Label(target)});
ctx.state.builder.AddFunction(spv::OpBranch, ctx.Label(target));
return;
}
case CFG::TerminatorKind::ConditionalBranch: {
@@ -162,8 +162,8 @@ void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
}
const auto condition = BranchCondition(ctx, info);
emit_merge();
ctx.state.builder.AddFunction({spv::OpBranchConditional, condition,
ctx.Label(true_block), ctx.Label(false_block)});
ctx.state.builder.AddFunction(spv::OpBranchConditional, condition,
ctx.Label(true_block), ctx.Label(false_block));
return;
}
default: EmitReturn(ctx); return;
@@ -192,10 +192,9 @@ uint32_t EmitDispatcherNextPc(ValueEmitContext& ctx, const DispatcherFunctionSta
EmitDispatcherTarget(ctx, dispatcher, block, term.true_block);
EmitDispatcherTarget(ctx, dispatcher, block, term.false_block);
const auto selected = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({spv::OpSelect, TypeU32(ctx.state), selected,
BranchCondition(ctx, info),
ConstantU32(ctx.state, term.true_block),
ConstantU32(ctx.state, term.false_block)});
ctx.state.builder.AddFunction(
spv::OpSelect, TypeU32(ctx.state), selected, BranchCondition(ctx, info),
ConstantU32(ctx.state, term.true_block), ConstantU32(ctx.state, term.false_block));
return selected;
}
case CFG::TerminatorKind::IndirectBranch: {
@@ -214,12 +213,10 @@ uint32_t EmitDispatcherNextPc(ValueEmitContext& ctx, const DispatcherFunctionSta
for (size_t index = 0; index < std::min(values.size(), targets.size()); index++) {
const auto match = ctx.state.builder.AllocateId();
const auto next = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({spv::OpIEqual, TypeBool(ctx.state), match,
selector,
ConstantU32(ctx.state, values[index])});
ctx.state.builder.AddFunction({spv::OpSelect, TypeU32(ctx.state), next, match,
ConstantU32(ctx.state, targets[index]),
selected});
ctx.state.builder.AddFunction(spv::OpIEqual, TypeBool(ctx.state), match,
selector, ConstantU32(ctx.state, values[index]));
ctx.state.builder.AddFunction(spv::OpSelect, TypeU32(ctx.state), next, match,
ConstantU32(ctx.state, targets[index]), selected);
selected = next;
}
}
@@ -307,15 +304,15 @@ void EmitDispatcherInstruction(ValueEmitContext& ctx, const DispatcherFunctionSt
if (type == 0) {
ctx.Fail(inst, "cannot be loaded by the dispatcher");
}
ctx.state.builder.AddFunction(
{spv::OpLoad, type, ctx.Result(inst), dispatcher.spills[ctx.half].at(&inst)});
ctx.state.builder.AddFunction(spv::OpLoad, type, ctx.Result(inst),
dispatcher.spills[ctx.half].at(&inst));
return;
}
EmitDirectInstruction(ctx, inst);
if (const auto found = dispatcher.spills[ctx.half].find(&inst);
found != dispatcher.spills[ctx.half].end()) {
ctx.state.builder.AddFunction(
{spv::OpStore, found->second, ctx.Def(IR::Value(const_cast<IR::Inst*>(&inst)))});
ctx.state.builder.AddFunction(spv::OpStore, found->second,
ctx.Def(IR::Value(const_cast<IR::Inst*>(&inst))));
}
}
@@ -362,7 +359,7 @@ void PatchStructuredPhis(ValueEmitContext& ctx, StructuredFunctionState& structu
void EmitStructuredFunction(ValueEmitContext& ctx) {
const auto& program = ctx.state.program;
StructuredFunctionState structured;
ctx.state.builder.AddFunction({spv::OpBranch, ctx.Label(program.blocks.front())});
ctx.state.builder.AddFunction(spv::OpBranch, ctx.Label(program.blocks.front()));
for (size_t index = 0; index < program.blocks.size(); index++) {
const auto* block = program.blocks[index];
EmitBlock(ctx, block, [&](ValueEmitContext& lane, const IR::Inst& inst) {
@@ -377,31 +374,31 @@ void EmitStructuredFunction(ValueEmitContext& ctx) {
void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher) {
auto& state = ctx.state;
const auto* entry = state.program.blocks.front();
state.builder.AddFunction({spv::OpBranch, ctx.Label(entry)});
state.builder.AddFunction(spv::OpBranch, ctx.Label(entry));
EmitBlock(ctx, entry, [&](ValueEmitContext& lane, const IR::Inst& inst) {
EmitDispatcherInstruction(lane, dispatcher, inst);
});
const auto initial_pc =
EmitDispatcherNextPc(ctx, dispatcher, entry, state.program.block_info.front());
const auto initial_parent = state.current_label;
state.builder.AddFunction({spv::OpBranch, dispatcher.header_label});
state.builder.AddFunction(spv::OpBranch, dispatcher.header_label);
EmitLabel(state, dispatcher.header_label);
const auto pc = state.builder.AllocateId();
const auto next_pc = state.builder.AllocateId();
state.builder.AddFunction({spv::OpPhi, TypeU32(state), pc, initial_pc, initial_parent, next_pc,
dispatcher.continue_label});
state.builder.AddFunction(spv::OpPhi, TypeU32(state), pc, initial_pc, initial_parent, next_pc,
dispatcher.continue_label);
const auto done = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpIEqual, TypeBool(state), done, pc, ConstantU32(ctx.state, UINT32_MAX)});
state.builder.AddFunction({spv::OpLoopMerge, dispatcher.merge_label, dispatcher.continue_label,
spv::LoopControlMaskNone});
state.builder.AddFunction(
{spv::OpBranchConditional, done, dispatcher.merge_label, dispatcher.select_label});
state.builder.AddFunction(spv::OpIEqual, TypeBool(state), done, pc,
ConstantU32(ctx.state, UINT32_MAX));
state.builder.AddFunction(spv::OpLoopMerge, dispatcher.merge_label, dispatcher.continue_label,
spv::LoopControlMaskNone);
state.builder.AddFunction(spv::OpBranchConditional, done, dispatcher.merge_label,
dispatcher.select_label);
EmitLabel(state, dispatcher.select_label);
state.builder.AddFunction(
{spv::OpSelectionMerge, dispatcher.after_switch_label, spv::SelectionControlMaskNone});
state.builder.AddFunction(spv::OpSelectionMerge, dispatcher.after_switch_label,
spv::SelectionControlMaskNone);
std::vector<uint32_t> words {spv::OpSwitch, pc, dispatcher.after_switch_label};
for (size_t index = 1; index < state.program.blocks.size(); index++) {
words.push_back(state.program.block_info[index].id);
@@ -420,13 +417,13 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
state.program.block_info[index]);
next_pc_words.push_back(selected);
next_pc_words.push_back(state.current_label);
state.builder.AddFunction({spv::OpBranch, dispatcher.after_switch_label});
state.builder.AddFunction(spv::OpBranch, dispatcher.after_switch_label);
}
EmitLabel(state, dispatcher.after_switch_label);
state.builder.AddFunction(next_pc_words);
state.builder.AddFunction({spv::OpBranch, dispatcher.continue_label});
state.builder.AddFunction(spv::OpBranch, dispatcher.continue_label);
EmitLabel(state, dispatcher.continue_label);
state.builder.AddFunction({spv::OpBranch, dispatcher.header_label});
state.builder.AddFunction(spv::OpBranch, dispatcher.header_label);
EmitLabel(state, dispatcher.merge_label);
EmitReturn(ctx);
}
@@ -478,7 +475,8 @@ uint32_t ValueEmitContext::Def(IR::Value value) {
return loaded->second.second;
}
const auto id = state.builder.AllocateId();
state.builder.AddFunction({spv::OpLoad, TypeId(state, inst->GetType()), id, found->second});
state.builder.AddFunction(spv::OpLoad, TypeId(state, inst->GetType()), id,
found->second);
dispatcher_block_loads.insert_or_assign(inst, std::pair {state.current_label, id});
return id;
}
@@ -498,9 +496,9 @@ uint32_t ValueEmitContext::Ballot(IR::Value predicate) {
const auto ballot_type = TypeU32Vector(state, 4);
const auto scope = ConstantU32(state, spv::ScopeSubgroup);
const auto low = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpGroupNonUniformBallot, ballot_type, low, scope,
other_half == nullptr || half == 0 ? Def(predicate) : other_half->Def(predicate)});
state.builder.AddFunction(spv::OpGroupNonUniformBallot, ballot_type, low, scope,
other_half == nullptr || half == 0 ? Def(predicate)
: other_half->Def(predicate));
if (other_half == nullptr) {
return low;
}
@@ -508,20 +506,20 @@ uint32_t ValueEmitContext::Ballot(IR::Value predicate) {
const auto low_word = state.builder.AllocateId();
const auto high_word = state.builder.AllocateId();
const auto ballot = state.builder.AllocateId();
state.builder.AddFunction({spv::OpGroupNonUniformBallot, ballot_type, high, scope,
half == 1 ? Def(predicate) : other_half->Def(predicate)});
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), low_word, low, 0});
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), high_word, high, 0});
state.builder.AddFunction({spv::OpCompositeConstruct, ballot_type, ballot, low_word, high_word,
ConstantU32(state, 0), ConstantU32(state, 0)});
state.builder.AddFunction(spv::OpGroupNonUniformBallot, ballot_type, high, scope,
half == 1 ? Def(predicate) : other_half->Def(predicate));
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), low_word, low, 0);
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), high_word, high, 0);
state.builder.AddFunction(spv::OpCompositeConstruct, ballot_type, ballot, low_word, high_word,
ConstantU32(state, 0), ConstantU32(state, 0));
return ballot;
}
uint32_t ValueEmitContext::FirstLane(uint32_t ballot) {
if (other_half == nullptr) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({spv::OpGroupNonUniformBallotFindLSB, TypeU32(state), result,
ConstantU32(state, spv::ScopeSubgroup), ballot});
state.builder.AddFunction(spv::OpGroupNonUniformBallotFindLSB, TypeU32(state), result,
ConstantU32(state, spv::ScopeSubgroup), ballot);
return result;
}
const auto low = state.builder.AllocateId();
@@ -530,16 +528,16 @@ uint32_t ValueEmitContext::FirstLane(uint32_t ballot) {
const auto high_first = state.builder.AllocateId();
const auto low_active = state.builder.AllocateId();
const auto result = state.builder.AllocateId();
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), low, ballot, 0});
state.builder.AddFunction({spv::OpCompositeExtract, TypeU32(state), high, ballot, 1});
state.builder.AddFunction(
{spv::OpExtInst, TypeU32(state), low_first, GlslStd450(state), GLSLstd450FindILsb, low});
state.builder.AddFunction(
{spv::OpExtInst, TypeU32(state), high_first, GlslStd450(state), GLSLstd450FindILsb, high});
state.builder.AddFunction(
{spv::OpINotEqual, TypeBool(state), low_active, low, ConstantU32(state, 0)});
state.builder.AddFunction({spv::OpSelect, TypeU32(state), result, low_active, low_first,
EmitAddU32(state, high_first, ConstantU32(state, 32))});
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), low, ballot, 0);
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), high, ballot, 1);
state.builder.AddFunction(spv::OpExtInst, TypeU32(state), low_first, GlslStd450(state),
GLSLstd450FindILsb, low);
state.builder.AddFunction(spv::OpExtInst, TypeU32(state), high_first, GlslStd450(state),
GLSLstd450FindILsb, high);
state.builder.AddFunction(spv::OpINotEqual, TypeBool(state), low_active, low,
ConstantU32(state, 0));
state.builder.AddFunction(spv::OpSelect, TypeU32(state), result, low_active, low_first,
EmitAddU32(state, high_first, ConstantU32(state, 32)));
return result;
}
@@ -548,8 +546,8 @@ uint32_t ValueEmitContext::Shuffle(const IR::Inst& inst, size_t index, uint32_t
const auto scope = ConstantU32(state, spv::ScopeSubgroup);
const auto low = state.builder.AllocateId();
if (other_half == nullptr) {
state.builder.AddFunction(
{spv::OpGroupNonUniformShuffle, type, low, scope, Arg(inst, index), lane});
state.builder.AddFunction(spv::OpGroupNonUniformShuffle, type, low, scope, Arg(inst, index),
lane);
return low;
}
const auto physical_lane =
@@ -557,15 +555,14 @@ uint32_t ValueEmitContext::Shuffle(const IR::Inst& inst, size_t index, uint32_t
const auto high = state.builder.AllocateId();
const auto in_high = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpGroupNonUniformShuffle, type, low, scope, HalfArg(inst, index, 0), physical_lane});
state.builder.AddFunction(
{spv::OpGroupNonUniformShuffle, type, high, scope, HalfArg(inst, index, 1), physical_lane});
state.builder.AddFunction(
{spv::OpINotEqual, TypeBool(state), in_high,
EmitBinaryU32(state, spv::OpBitwiseAnd, lane, ConstantU32(state, 32)),
ConstantU32(state, 0)});
state.builder.AddFunction({spv::OpSelect, type, value, in_high, high, low});
state.builder.AddFunction(spv::OpGroupNonUniformShuffle, type, low, scope,
HalfArg(inst, index, 0), physical_lane);
state.builder.AddFunction(spv::OpGroupNonUniformShuffle, type, high, scope,
HalfArg(inst, index, 1), physical_lane);
state.builder.AddFunction(spv::OpINotEqual, TypeBool(state), in_high,
EmitBinaryU32(state, spv::OpBitwiseAnd, lane, ConstantU32(state, 32)),
ConstantU32(state, 0));
state.builder.AddFunction(spv::OpSelect, type, value, in_high, high, low);
return value;
}
@@ -581,8 +578,8 @@ uint32_t ValueEmitContext::Result(const IR::Inst& inst) {
uint32_t ValueEmitContext::Define(const IR::Inst& inst, uint32_t value) {
if (const auto found = definitions.find(&inst); found != definitions.end()) {
if (found->second != value) {
state.builder.AddFunction(
{spv::OpCopyObject, TypeId(state, inst.GetType()), found->second, value});
state.builder.AddFunction(spv::OpCopyObject, TypeId(state, inst.GetType()),
found->second, value);
}
return found->second;
}
@@ -718,28 +715,28 @@ void EmitProgram(EmitterState& state) {
break;
}
}
state.builder.AddFunction({spv::OpFunction, TypeVoid(state),
state.mesh_guest_func != 0 ? state.mesh_guest_func : state.main_func,
spv::FunctionControlMaskNone, TypeFunction(state)});
state.builder.AddFunction(spv::OpFunction, TypeVoid(state),
state.mesh_guest_func != 0 ? state.mesh_guest_func : state.main_func,
spv::FunctionControlMaskNone, TypeFunction(state));
EmitLabel(state, state.entry_label);
if (state.requirements.function_lds) {
state.builder.AddFunction(
{spv::OpVariable,
TypeU32ArrayPointer(state, spv::StorageClassFunction, LdsDwordCount(state)),
state.lds_variable, spv::StorageClassFunction});
spv::OpVariable,
TypeU32ArrayPointer(state, spv::StorageClassFunction, LdsDwordCount(state)),
state.lds_variable, spv::StorageClassFunction);
}
if (state.requirements.function_scratch) {
for (uint32_t half = 0; half < state.lane_count; half++) {
state.builder.AddFunction({spv::OpVariable,
TypeU32ArrayPointer(state, spv::StorageClassFunction,
state.program.scratch_dwords),
state.scratch_variable[half], spv::StorageClassFunction});
state.builder.AddFunction(
spv::OpVariable,
TypeU32ArrayPointer(state, spv::StorageClassFunction, state.program.scratch_dwords),
state.scratch_variable[half], spv::StorageClassFunction);
}
}
if (state.pixel_valid_mask_variable != 0) {
state.builder.AddFunction({spv::OpVariable,
TypePointer(state, spv::StorageClassFunction, TypeU32(state)),
state.pixel_valid_mask_variable, spv::StorageClassFunction});
state.builder.AddFunction(spv::OpVariable,
TypePointer(state, spv::StorageClassFunction, TypeU32(state)),
state.pixel_valid_mask_variable, spv::StorageClassFunction);
}
for (uint32_t half = 0; half < state.lane_count; half++) {
auto& lane = half == 0 ? ctx : high;
@@ -748,27 +745,27 @@ void EmitProgram(EmitterState& state) {
for (const auto& inst: *block) {
if (const auto found = dispatcher->spills[half].find(&inst);
found != dispatcher->spills[half].end()) {
state.builder.AddFunction({spv::OpVariable,
SpillPointerType(lane, inst.GetType()),
found->second, spv::StorageClassFunction});
state.builder.AddFunction(spv::OpVariable,
SpillPointerType(lane, inst.GetType()),
found->second, spv::StorageClassFunction);
}
}
}
}
if (lane.scratch_u32_variable != 0) {
state.builder.AddFunction(
{spv::OpVariable, TypePointer(state, spv::StorageClassFunction, TypeU32(state)),
lane.scratch_u32_variable, spv::StorageClassFunction});
state.builder.AddFunction(spv::OpVariable,
TypePointer(state, spv::StorageClassFunction, TypeU32(state)),
lane.scratch_u32_variable, spv::StorageClassFunction);
}
}
if (state.gds_variable != 0) {
state.gds_length = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpArrayLength, TypeU32(state), state.gds_length, state.gds_variable, 0});
state.builder.AddFunction(spv::OpArrayLength, TypeU32(state), state.gds_length,
state.gds_variable, 0);
}
if (state.pixel_valid_mask_variable != 0) {
state.builder.AddFunction(
{spv::OpStore, state.pixel_valid_mask_variable, ConstantU32(state, 1)});
state.builder.AddFunction(spv::OpStore, state.pixel_valid_mask_variable,
ConstantU32(state, 1));
}
EmitMemoryOffsets(state);
if (program.blocks.empty()) {
@@ -778,7 +775,7 @@ void EmitProgram(EmitterState& state) {
} else {
EmitStructuredFunction(ctx);
}
state.builder.AddFunction({spv::OpFunctionEnd});
state.builder.AddFunction(spv::OpFunctionEnd);
if (state.program.stage == ShaderType::Mesh) {
EmitMeshEntryPoint(state);
}
@@ -0,0 +1,161 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
#include <algorithm>
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace {
uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
using Attribute = IR::TessellationAttribute;
const auto kind = static_cast<Attribute>(inst.Arg(0).U32());
const auto& tess = state.input_info.vertex->tess;
const auto variable = state.tess_variables.at(static_cast<uint32_t>(kind));
EXIT_IF(variable == 0);
const auto input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput;
const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput;
const auto pointer = state.builder.AllocateId();
if (kind == Attribute::Factor) {
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
const auto index = inst.Arg(1).U32() / 4u;
const auto outer = index < 3u;
EXIT_NOT_IMPLEMENTED(index >= 4u);
state.builder.AddFunction(spv::OpAccessChain, TypePointer(state, storage, TypeF32(state)),
pointer, outer ? variable : state.tess_inner_variable,
ConstantU32(state, outer ? index : index - 3u));
return pointer;
}
auto address = ctx.Arg(inst, 1);
if (kind == Attribute::PatchOutput) {
address =
EmitBinaryU32(state, spv::OpISub, address, ConstantU32(state, state.tess_patch_base));
}
const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput;
const auto stride = local ? tess.ls_stride : tess.hs_stride;
const auto offset = kind == Attribute::PatchOutput ? address
: EmitBinaryU32(state, spv::OpUMod, address,
ConstantU32(state, stride));
const auto attribute =
EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 4));
const auto component =
EmitBinaryU32(state, spv::OpBitwiseAnd,
EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 2)),
ConstantU32(state, 3));
const auto type = TypePointer(state, storage, TypeU32(state));
if (kind == Attribute::LocalOutput || kind == Attribute::PatchOutput) {
state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, attribute,
component);
} else {
const auto vertex =
kind == Attribute::ControlOutput
? EmitLaneId(state)
: EmitBinaryU32(state, spv::OpUDiv, address, ConstantU32(state, stride));
state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, vertex, attribute,
component);
}
return pointer;
}
} // namespace
void DefineTessellationInterfaces(EmitterState& state) {
using Attribute = IR::TessellationAttribute;
std::array<bool, 6> used {};
uint32_t patch_begin = UINT32_MAX, patch_end = 0;
for (const auto* block: state.program.blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() != IR::ValueOpcode::GetTessellationAttribute &&
inst.GetOpcode() != IR::ValueOpcode::SetTessellationAttribute)
continue;
const auto kind = inst.Arg(0).U32();
used.at(kind) = true;
if (kind == static_cast<uint32_t>(Attribute::PatchOutput)) {
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
patch_begin = std::min(patch_begin, inst.Arg(1).U32());
patch_end = std::max(patch_end, inst.Arg(1).U32() + 4u);
}
}
}
if (std::ranges::none_of(used, [](bool value) { return value; })) return;
const auto& tess = state.input_info.vertex->tess;
const auto array = [&](uint32_t type, uint32_t count) {
return state.builder.Type(spv::OpTypeArray, type, ConstantU32(state, count));
};
for (uint32_t index = 0; index < used.size(); index++) {
if (!used[index]) continue;
const auto kind = static_cast<Attribute>(index);
const bool input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput;
const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput;
uint32_t type;
if (kind == Attribute::Factor) {
type = array(TypeF32(state), 4u);
} else if (kind == Attribute::PatchOutput) {
state.tess_patch_base = patch_begin;
type = array(TypeU32Vector(state, 4), (patch_end - patch_begin + 15u) / 16u);
} else {
const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput;
const auto stride = local ? tess.ls_stride : tess.hs_stride;
type = array(TypeU32Vector(state, 4), (stride + 15u) / 16u);
if (kind != Attribute::LocalOutput) {
type = array(type, local ? tess.input_control_points : tess.output_control_points);
}
}
auto& variable = state.tess_variables[index];
variable = DefineInterfaceVariable(state, type, storage, "tess_attributes");
if (kind == Attribute::Factor) {
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBuiltIn,
spv::BuiltInTessLevelOuter);
state.tess_inner_variable =
DefineInterfaceVariable(state, array(TypeF32(state), 2u), storage, "tess_inner");
state.builder.AddAnnotation(spv::OpDecorate, state.tess_inner_variable,
spv::DecorationBuiltIn, spv::BuiltInTessLevelInner);
state.builder.AddAnnotation(spv::OpDecorate, state.tess_inner_variable,
spv::DecorationPatch);
} else {
state.builder.AddAnnotation(
spv::OpDecorate, variable, spv::DecorationLocation,
kind == Attribute::PatchOutput ? (tess.hs_stride + 15u) / 16u : 0u);
}
if (kind == Attribute::Factor || kind == Attribute::PatchOutput) {
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationPatch);
}
}
}
void DefineTessellationExecutionModes(EmitterState& state) {
const auto& tess = state.input_info.vertex->tess;
EXIT_NOT_IMPLEMENTED(tess.domain != 1u || tess.partitioning != 2u ||
tess.output_topology != 2u);
state.builder.RequireCapability(spv::CapabilityTessellation);
if (state.program.stage == ShaderType::TessellationControl) {
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputVertices,
tess.output_control_points);
} else {
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeTriangles);
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeSpacingFractionalOdd);
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeVertexOrderCw);
}
}
uint32_t EmitGetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, 2), [&] {
const auto value = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), value,
TessellationPointer(ctx, inst));
return value;
});
}
void EmitSetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
EmitIfCondition(ctx.state, ctx.Arg(inst, 3), [&] {
auto value = ctx.Arg(inst, 2);
if (inst.Arg(0).U32() == static_cast<uint32_t>(IR::TessellationAttribute::Factor)) {
const auto floating = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(spv::OpBitcast, TypeF32(ctx.state), floating, value);
value = floating;
}
ctx.state.builder.AddFunction(spv::OpStore, TessellationPointer(ctx, inst), value);
});
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -381,6 +381,25 @@ void DecodeInstruction(std::span<const uint32_t> code, uint32_t word_index, Inst
}
}
Program DecodeFrontProgram(std::span<const uint32_t> front) {
Program result;
uint32_t front_words = 0;
while (front_words < front.size()) {
auto& inst = result.instructions.emplace_back();
DecodeInstruction(front, front_words, inst);
front_words += inst.word_count;
if (inst.opcode == Opcode::S_SETPC_B64) {
EXIT_NOT_IMPLEMENTED(inst.src0.kind != OperandKind::Sgpr || inst.src0.reg != 6u);
break;
}
EXIT_NOT_IMPLEMENTED(inst.opcode == Opcode::S_ENDPGM);
}
EXIT_IF(result.instructions.empty() ||
result.instructions.back().opcode != Opcode::S_SETPC_B64);
result.code = front.first(front_words);
return result;
}
void DecodeProgram(std::span<const uint32_t> code, Program& program) {
program.instructions.clear();
program.instructions.reserve(code.size());
@@ -190,6 +190,7 @@ enum class Opcode {
V_CEIL_F16,
V_TRUNC_F16,
V_RNDNE_F16,
V_FRACT_F16,
V_SIN_F16,
V_COS_F16,
V_SIN_F32,
@@ -395,6 +396,7 @@ enum class Opcode {
V_CMP_EQ_U16,
V_CMP_LE_U16,
V_CMP_GT_U16,
V_CMPX_LT_U16,
V_CMPX_GT_U16,
V_CMP_NE_U16,
V_CMP_GE_U16,
@@ -720,6 +722,7 @@ struct Program {
Family GetInstructionFamily(uint32_t word);
// The output object must be freshly initialized.
void DecodeInstruction(std::span<const uint32_t> code, uint32_t word_index, Instruction& inst);
Program DecodeFrontProgram(std::span<const uint32_t> front);
void DecodeProgram(std::span<const uint32_t> code, Program& program);
bool IsConditionalBranch(Opcode opcode);
bool IsDirectBranch(Opcode opcode);
@@ -139,6 +139,7 @@ constexpr OpcodeMap VOP1_OPCODE_LIST[] = {
{0x5cu, Opcode::V_CEIL_F16},
{0x5du, Opcode::V_TRUNC_F16},
{0x5eu, Opcode::V_RNDNE_F16},
{0x5fu, Opcode::V_FRACT_F16},
{0x60u, Opcode::V_SIN_F16},
{0x61u, Opcode::V_COS_F16},
};
@@ -192,6 +193,7 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = {
{0x5cu, Opcode::V_CEIL_F16},
{0x5du, Opcode::V_TRUNC_F16},
{0x5eu, Opcode::V_RNDNE_F16},
{0x5fu, Opcode::V_FRACT_F16},
{0x60u, Opcode::V_SIN_F16},
{0x61u, Opcode::V_COS_F16},
};
@@ -233,6 +235,7 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
{0xa9u, Opcode::V_CMP_LT_U16}, {0xaau, Opcode::V_CMP_EQ_U16},
{0xabu, Opcode::V_CMP_LE_U16}, {0xacu, Opcode::V_CMP_GT_U16},
{0xadu, Opcode::V_CMP_NE_U16}, {0xaeu, Opcode::V_CMP_GE_U16},
{0xb9u, Opcode::V_CMPX_LT_U16, false},
{0xbcu, Opcode::V_CMPX_GT_U16}, {0xc0u, Opcode::V_CMP_F_U32},
{0xc1u, Opcode::V_CMP_LT_U32}, {0xc2u, Opcode::V_CMP_EQ_U32},
{0xc3u, Opcode::V_CMP_LE_U32}, {0xc4u, Opcode::V_CMP_GT_U32},
@@ -479,6 +482,7 @@ bool IsVop1FloatSourceOpcode(Opcode opcode) {
case Opcode::V_CEIL_F16:
case Opcode::V_TRUNC_F16:
case Opcode::V_RNDNE_F16:
case Opcode::V_FRACT_F16:
case Opcode::V_SIN_F16:
case Opcode::V_COS_F16:
case Opcode::V_SIN_F32:
@@ -548,6 +552,8 @@ constexpr Vop1SdwaRule VOP1_SDWA_RULES[] = {
SdwaSelWords() | SdwaSelFull(), true},
{Opcode::V_RNDNE_F16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
SdwaSelWords() | SdwaSelFull(), true},
{Opcode::V_FRACT_F16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
SdwaSelWords() | SdwaSelFull(), true},
{Opcode::V_SIN_F16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
SdwaSelWords() | SdwaSelFull(), true},
{Opcode::V_COS_F16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
@@ -773,6 +779,7 @@ bool IsVop1FloatResultOpcode(Opcode opcode) {
case Opcode::V_CEIL_F16:
case Opcode::V_TRUNC_F16:
case Opcode::V_RNDNE_F16:
case Opcode::V_FRACT_F16:
case Opcode::V_SIN_F16:
case Opcode::V_COS_F16:
case Opcode::V_SIN_F32:
@@ -1434,6 +1441,7 @@ bool IsVopcCompareExec(Opcode opcode) {
case Opcode::V_CMPX_GE_U32:
case Opcode::V_CMPX_NE_I64:
case Opcode::V_CMPX_NE_U64:
case Opcode::V_CMPX_LT_U16:
case Opcode::V_CMPX_GT_U16:
case Opcode::V_CMPX_LT_F16:
case Opcode::V_CMPX_EQ_F16:
@@ -898,11 +898,15 @@ void ValidateTranslateOptions(const TranslateOptions& options) {
if (options.wave_size != 32u && options.wave_size != 64u) {
EXIT("shader translation requires wave32 or wave64, got %u", options.wave_size);
}
if (options.embedded_fetch != nullptr && options.stage != ShaderType::Vertex) {
EXIT("embedded fetch requires the vertex shader stage");
if (options.embedded_fetch != nullptr && options.stage != ShaderType::Vertex &&
options.stage != ShaderType::Local) {
EXIT("embedded fetch requires a vertex or local shader");
}
switch (options.stage) {
case ShaderType::Vertex:
case ShaderType::Local:
case ShaderType::TessellationControl:
case ShaderType::TessellationEvaluation:
case ShaderType::Mesh:
if (options.input_info.vertex == nullptr) {
EXIT("vertex shader translation has no vertex input metadata");
@@ -941,6 +945,9 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
result.user_data_count = options.user_data_count;
switch (options.stage) {
case ShaderType::Vertex:
case ShaderType::Local:
case ShaderType::TessellationControl:
case ShaderType::TessellationEvaluation:
result.scratch_dwords = options.input_info.vertex->scratch_size_dwords;
break;
case ShaderType::Mesh:
@@ -1164,6 +1171,42 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
entry_ir.SetVectorReg(
static_cast<IR::VectorReg>(8),
entry_ir.IAdd(draw(2), builtin(IR::StageInputKind::WorkgroupId, 1)));
} else if (options.stage == ShaderType::Local) {
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), IR::U32(IR::Value(64u)));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(2),
builtin(IR::StageInputKind::VertexIndex));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(3), IR::U32(IR::Value(0u)));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
builtin(IR::StageInputKind::InstanceIndex));
} else if (options.stage == ShaderType::TessellationControl) {
const auto& tess = options.input_info.vertex->tess;
entry_ir.SetScalarReg(
static_cast<IR::ScalarReg>(2),
IR::U32(entry_ir.Emit(IR::ValueOpcode::TessellationBase, {IR::Value(0u)})));
entry_ir.SetScalarReg(
static_cast<IR::ScalarReg>(4),
IR::U32(entry_ir.Emit(IR::ValueOpcode::TessellationBase, {IR::Value(1u)})));
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3),
IR::U32(IR::Value(0x81010000u | tess.input_control_points |
(tess.output_control_points << 8u))));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(0),
builtin(IR::StageInputKind::PrimitiveId));
entry_ir.SetVectorReg(
static_cast<IR::VectorReg>(1),
entry_ir.ShiftLeftLogical(builtin(IR::StageInputKind::InvocationId),
IR::U32(IR::Value(8u))));
} else if (options.stage == ShaderType::TessellationEvaluation) {
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), IR::U32(IR::Value(64u)));
entry_ir.SetScalarReg(
static_cast<IR::ScalarReg>(4),
IR::U32(entry_ir.Emit(IR::ValueOpcode::TessellationBase, {IR::Value(0u)})));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
builtin(IR::StageInputKind::TessCoord, 0));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(6),
builtin(IR::StageInputKind::TessCoord, 1));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(7), IR::U32(IR::Value(0u)));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
builtin(IR::StageInputKind::PrimitiveId));
} else if (options.stage == ShaderType::Pixel) {
const auto* ps = options.input_info.pixel;
if (ps->ps_perspective_center_vgpr != UINT32_MAX) {
@@ -138,6 +138,9 @@ bool Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_CMP_LT_U16:
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false, false);
return true;
case O::V_CMPX_LT_U16:
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false, true);
return true;
case O::V_CMP_LE_U16:
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThanEqual32, false, false);
return true;
@@ -370,6 +373,7 @@ bool Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_CEIL_F16: return Float16Unary(inst, IR::ValueOpcode::FPCeil32, false);
case O::V_TRUNC_F16: return Float16Unary(inst, IR::ValueOpcode::FPTrunc32, false);
case O::V_RNDNE_F16: return Float16Unary(inst, IR::ValueOpcode::FPRoundEven32, false);
case O::V_FRACT_F16: return Float16Unary(inst, IR::ValueOpcode::FPFract32, false);
case O::V_SIN_F16: return Float16Trig(inst, IR::ValueOpcode::FPSin);
case O::V_COS_F16: return Float16Trig(inst, IR::ValueOpcode::FPCos);
case O::V_MIN3_F16: return Float16Ternary(inst, IR::ValueOpcode::FPMinTri32, false, false);
+17 -1
View File
@@ -150,8 +150,20 @@ struct SampledResourcePair {
bool operator==(const SampledResourcePair& other) const = default;
};
enum class TessellationAttribute {
LocalOutput,
ControlInput,
ControlOutput,
EvaluationInput,
PatchOutput,
Factor
};
enum class StageInputKind {
VertexIndex,
InvocationId,
PrimitiveId,
TessCoord,
InstanceIndex,
FragCoord,
FrontFacing,
@@ -287,8 +299,12 @@ static_assert(sizeof(PushData) == 128);
constexpr uint32_t NativePushConstantSize = sizeof(PushData);
[[nodiscard]] constexpr uint32_t NativeBinding(ShaderType stage, DescriptorBindingKind kind) {
const uint32_t group = stage == ShaderType::Pixel ? 1u
: stage == ShaderType::TessellationControl ? 2u
: stage == ShaderType::TessellationEvaluation ? 3u
: 0u;
return static_cast<uint32_t>(kind) +
(stage == ShaderType::Pixel ? static_cast<uint32_t>(DescriptorBindingKind::Count) : 0u);
group * static_cast<uint32_t>(DescriptorBindingKind::Count);
}
[[nodiscard]] constexpr ImageResourceClass ImageBindingResourceClass(DescriptorBindingKind kind) {
@@ -90,6 +90,7 @@ bool HasSideEffects(ValueOpcode opcode) {
case ValueOpcode::Reference:
case ValueOpcode::ReferenceU32:
case ValueOpcode::SetAttribute:
case ValueOpcode::SetTessellationAttribute:
case ValueOpcode::MeshAllocate:
case ValueOpcode::Barrier: return true;
default: return false;
@@ -10,6 +10,9 @@ VALUE_OPCODE(GetUserData, U32, ScalarReg)
VALUE_OPCODE(GetShaderBase, U64)
VALUE_OPCODE(MeshDrawParameter, U32, U32)
VALUE_OPCODE(MeshAllocate, Void, U32)
VALUE_OPCODE(TessellationBase, U32, U32)
VALUE_OPCODE(GetTessellationAttribute, U32, U32, U32, U1)
VALUE_OPCODE(SetTessellationAttribute, Void, U32, U32, U32, U1)
VALUE_OPCODE(GetBuiltin, U32, U32, U32)
VALUE_OPCODE(GetThreadBitScalarRegister, U1, ScalarReg)
VALUE_OPCODE(SetThreadBitScalarRegister, Void, ScalarReg, U1)
@@ -228,6 +228,15 @@ void FoldInstruction(Block& block, Block::iterator instruction,
const auto offset = Arg(inst, 1);
const auto count = Arg(inst, 2);
auto* source = value.TryInstruction();
if (source != nullptr && source->GetOpcode() == ValueOpcode::ShiftLeftLogical32 &&
IsImmediate(offset, Type::U32) && IsImmediate(count, Type::U32)) {
const auto shift = Arg(*source, 1);
if (IsImmediate(shift, Type::U32) && shift.U32() < 32u &&
offset.U32() <= shift.U32() && count.U32() <= shift.U32() - offset.U32()) {
Replace(inst, Value(0u));
return;
}
}
if (source != nullptr && source->GetOpcode() == ValueOpcode::GetBuiltin &&
source->Arg(0) == Value(static_cast<uint32_t>(StageInputKind::PackedAncillary)) &&
IsImmediate(offset, Type::U32) && IsImmediate(count, Type::U32) && count.U32() != 0u) {
@@ -43,6 +43,9 @@ void AddOutput(ShaderInfo& info, StageOutputKind kind, uint32_t index, uint32_t
void ValidateOptions(const Program& program, ShaderStageInputInfo input_info) {
switch (program.stage) {
case ShaderType::Vertex:
case ShaderType::Local:
case ShaderType::TessellationControl:
case ShaderType::TessellationEvaluation:
case ShaderType::Mesh:
if (input_info.vertex == nullptr) {
return Fail("vertex shader has no input metadata");
@@ -81,7 +84,8 @@ void ValidateValueReferences(const Program& program, ShaderStageInputInfo input_
!inst.Arg(1).IsImmediate() || inst.Arg(1).GetType() != Type::U32) {
return Fail("typed attribute reference is not constant");
}
if (program.stage == ShaderType::Vertex &&
if ((program.stage == ShaderType::Vertex ||
program.stage == ShaderType::Local) &&
(inst.Arg(1).U32() >= 4u ||
inst.Arg(0).U32() >=
static_cast<uint32_t>(input_info.vertex->resources_num))) {
@@ -124,6 +128,8 @@ void ValidateValueReferences(const Program& program, ShaderStageInputInfo input_
}
break;
case StageInputKind::VertexIndex:
case StageInputKind::InvocationId:
case StageInputKind::PrimitiveId:
case StageInputKind::InstanceIndex:
case StageInputKind::FrontFacing:
case StageInputKind::LocalInvocationIndex:
@@ -142,6 +148,7 @@ void ValidateValueReferences(const Program& program, ShaderStageInputInfo input_
return Fail("typed barycentric component is out of range");
}
break;
case StageInputKind::TessCoord:
case StageInputKind::WorkgroupId:
case StageInputKind::LocalInvocationId:
case StageInputKind::GlobalInvocationId:
@@ -161,8 +168,10 @@ void ValidateValueReferences(const Program& program, ShaderStageInputInfo input_
}
const auto& exp = program.export_info[index];
if (exp.kind == ExportTargetKind::Position && exp.index != 0 && exp.en != 0 &&
program.stage != ShaderType::Vertex && program.stage != ShaderType::Mesh) {
return Fail("auxiliary position export requires the vertex or mesh stage");
program.stage != ShaderType::Vertex && program.stage != ShaderType::Mesh &&
program.stage != ShaderType::TessellationEvaluation) {
return Fail("auxiliary position export requires a vertex, mesh, or "
"tessellation evaluation shader");
}
break;
}
@@ -249,6 +258,10 @@ void CollectComputeInputs(const ShaderComputeInputInfo* compute, ShaderInfo& inf
void CollectBuiltinInputs(const Program& program, ShaderInfo& info) {
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
if (program.stage == ShaderType::TessellationControl &&
inst.GetOpcode() == ValueOpcode::LaneId) {
AddInput(info, StageInputKind::InvocationId, 0, 1, "gl_InvocationID");
}
if (inst.GetOpcode() != ValueOpcode::GetBuiltin) {
continue;
}
@@ -260,6 +273,13 @@ void CollectBuiltinInputs(const Program& program, ShaderInfo& info) {
case StageInputKind::InstanceIndex:
AddInput(info, kind, 0, 1, "gl_InstanceIndex");
break;
case StageInputKind::InvocationId:
AddInput(info, kind, 0, 1, "gl_InvocationID");
break;
case StageInputKind::PrimitiveId:
AddInput(info, kind, 0, 1, "gl_PrimitiveID");
break;
case StageInputKind::TessCoord: AddInput(info, kind, 0, 3, "gl_TessCoord"); break;
case StageInputKind::FragCoord: AddInput(info, kind, 0, 4, "gl_FragCoord"); break;
case StageInputKind::FrontFacing:
AddInput(info, kind, 0, 1, "gl_FrontFacing");
@@ -380,7 +400,10 @@ void CollectShaderInfo(Program& program, ShaderStageInputInfo input_info) {
});
});
switch (program.stage) {
case ShaderType::Vertex: CollectVertexInputs(program, input_info.vertex, next); break;
case ShaderType::Vertex:
case ShaderType::Local: CollectVertexInputs(program, input_info.vertex, next); break;
case ShaderType::TessellationControl:
case ShaderType::TessellationEvaluation:
case ShaderType::Mesh: break;
case ShaderType::Pixel: CollectPixelInputs(program, input_info.pixel, next); break;
case ShaderType::Compute: CollectComputeInputs(input_info.compute, next); break;
+16 -15
View File
@@ -55,7 +55,7 @@ class RectListEmitter {
public:
RectListEmitter(const std::vector<Parameter>& parameters_, spv::ExecutionModel model)
: parameters(parameters_) {
builder.AddMemoryModel({spv::AddressingModelLogical, spv::MemoryModelGLSL450});
builder.AddMemoryModel(spv::AddressingModelLogical, spv::MemoryModelGLSL450);
void_type = Type(spv::OpTypeVoid);
uint_type = Type(spv::OpTypeInt, 32u, 0u);
@@ -65,9 +65,10 @@ public:
function_type = Type(spv::OpTypeFunction, void_type);
per_vertex_type = builder.DecoratedType(
spv::OpTypeStruct, {vec4_float_type},
spv::OpTypeStruct,
{{spv::OpMemberDecorate, {0u, spv::DecorationBuiltIn, spv::BuiltInPosition}},
{spv::OpDecorate, {spv::DecorationBlock}}});
{spv::OpDecorate, {spv::DecorationBlock}}},
vec4_float_type);
ptr_input_vec4_float = Pointer(spv::StorageClassInput, vec4_float_type);
ptr_output_vec4_float = Pointer(spv::StorageClassOutput, vec4_float_type);
@@ -188,11 +189,11 @@ public:
private:
template <typename... Args>
uint32_t Type(spv::Op opcode, Args... operands) {
return builder.Type(opcode, {operands...});
return builder.Type(opcode, operands...);
}
uint32_t Constant(uint32_t type, uint32_t value) {
return builder.Constant(spv::OpConstant, type, {value});
return builder.Constant(spv::OpConstant, type, value);
}
uint32_t Pointer(spv::StorageClass storage, uint32_t type) {
@@ -206,20 +207,20 @@ private:
template <typename... Args>
uint32_t Result(spv::Op opcode, uint32_t type, Args... operands) {
const auto id = builder.AllocateId();
builder.AddFunction({opcode, type, id, operands...});
builder.AddFunction(opcode, type, id, operands...);
return id;
}
template <typename... Args>
uint32_t ResultWithoutType(spv::Op opcode, Args... operands) {
const auto id = builder.AllocateId();
builder.AddFunction({opcode, id, operands...});
builder.AddFunction(opcode, id, operands...);
return id;
}
template <typename... Args>
void Emit(spv::Op opcode, Args... operands) {
builder.AddFunction({opcode, operands...});
builder.AddFunction(opcode, operands...);
}
template <typename... Args>
@@ -242,7 +243,7 @@ private:
}
void Decorate(uint32_t target, spv::Decoration decoration, uint32_t value) {
builder.AddAnnotation({spv::OpDecorate, target, decoration, value});
builder.AddAnnotation(spv::OpDecorate, target, decoration, value);
}
void DefineEntry(spv::ExecutionModel model) {
@@ -250,11 +251,11 @@ private:
builder.RequireCapability(spv::CapabilityTessellation);
main = Result(spv::OpFunction, void_type, spv::FunctionControlMaskNone, function_type);
if (model == spv::ExecutionModelTessellationControl) {
builder.AddExecutionMode({main, spv::ExecutionModeOutputVertices, 4u});
builder.AddExecutionMode(main, spv::ExecutionModeOutputVertices, 4u);
} else {
builder.AddExecutionMode({main, spv::ExecutionModeQuads});
builder.AddExecutionMode({main, spv::ExecutionModeSpacingEqual});
builder.AddExecutionMode({main, spv::ExecutionModeVertexOrderCw});
builder.AddExecutionMode(main, spv::ExecutionModeQuads);
builder.AddExecutionMode(main, spv::ExecutionModeSpacingEqual);
builder.AddExecutionMode(main, spv::ExecutionModeVertexOrderCw);
}
DefineInputs(model);
DefineOutputs(model);
@@ -296,10 +297,10 @@ private:
gl_out = AddInterface(spv::StorageClassOutput, Array(per_vertex_type, 4u));
tess_inner = AddInterface(spv::StorageClassOutput, Array(float_type, 2u));
Decorate(tess_inner, spv::DecorationBuiltIn, spv::BuiltInTessLevelInner);
builder.AddAnnotation({spv::OpDecorate, tess_inner, spv::DecorationPatch});
builder.AddAnnotation(spv::OpDecorate, tess_inner, spv::DecorationPatch);
tess_outer = AddInterface(spv::StorageClassOutput, Array(float_type, 4u));
Decorate(tess_outer, spv::DecorationBuiltIn, spv::BuiltInTessLevelOuter);
builder.AddAnnotation({spv::OpDecorate, tess_outer, spv::DecorationPatch});
builder.AddAnnotation(spv::OpDecorate, tess_outer, spv::DecorationPatch);
} else {
gl_out = AddInterface(spv::StorageClassOutput, per_vertex_type);
}
+85 -18
View File
@@ -11,6 +11,8 @@
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/shader/recompiler/ShaderRecompiler.h"
#include "graphics/shader/recompiler/Tessellation.h"
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
#include "graphics/shader/shaderCompiler.h"
#include "graphics/shader/shaderVertexMetadata.h"
@@ -511,33 +513,28 @@ static uint32_t ShaderCalcPsSystemInputBase(const HW::ShaderRegisters& regs) {
return reg;
}
static bool ShaderGetStaticInputInfoVS(const HW::VertexShaderInfo& regs,
const HW::ShaderRegisters& sh,
const ShaderMappedData& data,
ShaderVertexInputInfo& info) {
static bool ShaderGetStaticVertexInputInfo(uint64_t shader_addr, const HW::UserSgprInfo& user_sgpr,
uint32_t user_sgpr_num, const HW::ShaderRegisters& sh,
const ShaderMappedData& data,
ShaderVertexInputInfo& info) {
KYTY_PROFILER_FUNCTION();
info = {};
info.pa_cl_vs_out_cntl = sh.m_paClVsOutCntl;
EXIT_NOT_IMPLEMENTED(regs.es_regs.data_addr == 0);
uint64_t shader_addr = regs.es_regs.data_addr;
const HW::UserSgprInfo& user_sgpr = regs.gs_user_sgpr;
auto user_sgpr_num = regs.gs_regs.rsrc2.user_sgpr;
EXIT_NOT_IMPLEMENTED(shader_addr == 0);
info.scratch_size_dwords = data.scratch_size_dwords;
if (data.user_data == nullptr) {
LOGF("ShaderGetInputInfoVS(): no AGC user data for shader=0x%016" PRIx64 " es=0x%016" PRIx64
" gs=0x%016" PRIx64 " user_sgpr_num=%u\n",
shader_addr, regs.es_regs.data_addr, regs.gs_regs.data_addr,
static_cast<uint32_t>(user_sgpr_num));
LOGF("ShaderGetVertexInputInfo(): no AGC user data for shader=0x%016" PRIx64
" user_sgpr_num=%u\n",
shader_addr, user_sgpr_num);
}
ShaderVertexMetadata metadata;
std::string metadata_error;
if (!ShaderReadVertexMetadata(data, HW::UserSgprInfo::SGPRS_MAX, metadata, &metadata_error)) {
LOGF("ShaderGetInputInfoVS(): invalid AGC metadata shader=0x%016" PRIx64 ": %s\n",
LOGF("ShaderGetVertexInputInfo(): invalid AGC metadata shader=0x%016" PRIx64 ": %s\n",
shader_addr, metadata_error.c_str());
return false;
}
@@ -556,7 +553,7 @@ static bool ShaderGetStaticInputInfoVS(const HW::VertexShaderInfo& regs,
(static_cast<uint64_t>(user_sgpr.value[metadata.vertex_buffer_reg + 1]) << 32u));
if (attrib == nullptr || buffer == nullptr) {
LOGF("ShaderGetInputInfoVS(): null vertex table pointer shader=0x%016" PRIx64 "\n",
LOGF("ShaderGetVertexInputInfo(): null vertex table pointer shader=0x%016" PRIx64 "\n",
shader_addr);
return false;
}
@@ -576,9 +573,11 @@ static void ShaderGetStaticInputInfoPS(
ps_info = {};
ps_info.scratch_size_dwords = data.scratch_size_dwords;
// SPI_PS_IN_CONTROL.NUM_INTERP occupies bits 5:0. Keep the remaining control
// flags in the hardware state and extract only the input count here.
// SPI_PS_IN_CONTROL: NUM_INTERP occupies bits 5:0 and PS_W32_EN is bit 15.
ps_info.input_num = sh.ps_in_control & 0x3fu;
if ((sh.ps_in_control & 0x8000u) != 0) {
ps_info.wave_size = 32;
}
EXIT_NOT_IMPLEMENTED(ps_info.input_num > std::size(ps_info.interpolator_settings));
ps_info.ps_system_input_base = ShaderCalcPsSystemInputBase(sh);
const uint32_t active_inputs = sh.ps_input_ena & sh.ps_input_addr;
@@ -673,6 +672,12 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t
mesh.primitives_per_group, mesh.vertices_per_group,
mesh.max_vertices, mesh.max_primitives, mesh.provoking_vertex});
}
key.push_back(info.tess.input_control_points);
if (info.tess.input_control_points != 0) {
const auto& tess = info.tess;
key.insert(key.end(), {tess.output_control_points, tess.ls_stride, tess.hs_stride,
tess.domain, tess.partitioning, tess.output_topology});
}
for (int i = 0; i < info.resources_num; i++) {
const auto& resource = info.resources[i];
@@ -698,6 +703,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
key.clear();
key.push_back(info.scratch_size_dwords);
key.push_back(info.input_num);
key.push_back(info.wave_size);
key.push_back(info.ps_system_input_base);
key.push_back(info.custom_interpolation_mask);
key.push_back(info.ps_perspective_center_vgpr);
@@ -750,7 +756,8 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
GetDeclaredShaderHash(regs.es_regs.data_addr),
std::span<const uint32_t>(regs.gs_user_sgpr.value, regs.gs_regs.rsrc2.user_sgpr), data);
if ((context.GetShaderStages() & 0x20u) == 0) {
if (!ShaderGetStaticInputInfoVS(regs, sh, data, info)) {
if (!ShaderGetStaticVertexInputInfo(regs.es_regs.data_addr, regs.gs_user_sgpr,
regs.gs_regs.rsrc2.user_sgpr, sh, data, info)) {
EXIT("failed to prepare vertex shader program\n");
}
return params;
@@ -759,6 +766,7 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
// its user-data pointer in s0:s1.
params.user_data.insert(params.user_data.begin(), 8u, 0u);
info = {};
info.logical_stage = ShaderType::Mesh;
info.pa_cl_vs_out_cntl = sh.m_paClVsOutCntl;
auto& mesh = info.mesh;
mesh.input_primitive = static_cast<uint32_t>(user_config.GetPrimType());
@@ -806,6 +814,65 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
return params;
}
std::array<ShaderParams, 3>
PrepareTessellationPrograms(const HW::VertexShaderInfo& regs, const HW::Context& context,
std::array<ShaderVertexInputInfo, 3>& input_info) {
const auto& sh = context.GetShaderRegisters();
const auto local = ShaderGetMappedData(regs.ls_regs.data_addr, "ShaderGetInputInfoLS():");
const auto control = ShaderGetMappedData(regs.hs_regs.data_addr, "ShaderGetInputInfoHS():");
const auto evaluation = ShaderGetMappedData(regs.es_regs.data_addr, "ShaderGetInputInfoTES():");
EXIT_NOT_IMPLEMENTED(local.type != Prospero::ShaderBinaryType::kHsFront ||
control.type != Prospero::ShaderBinaryType::kHsBack ||
evaluation.type != Prospero::ShaderBinaryType::kGs);
EXIT_NOT_IMPLEMENTED((context.GetShaderStages() & 0x4u) == 0 ||
(context.GetShaderStages() & 0x00600020u) != 0);
EXIT_IF(regs.hs_regs.user_data_addr == 0);
const auto local_users = std::span(regs.hs_user_sgpr.value, regs.hs_regs.rsrc2.user_sgpr);
const auto evaluation_users = std::span(regs.gs_user_sgpr.value, regs.gs_regs.rsrc2.user_sgpr);
std::array<ShaderParams, 3> params {
GetShaderParams(regs.ls_regs.data_addr, "ShaderRecompiler LS",
GetDeclaredShaderHash(regs.ls_regs.data_addr), local_users, local),
GetShaderParams(regs.hs_regs.data_addr, "ShaderRecompiler HS",
GetDeclaredShaderHash(regs.hs_regs.data_addr), local_users, control),
GetShaderParams(regs.es_regs.data_addr, "ShaderRecompiler TES",
GetDeclaredShaderHash(regs.es_regs.data_addr), evaluation_users,
evaluation),
};
// The fused HS back half receives its separate user-data address in s0:s1.
// RDNA2 reserves s0:s7 before the native HS user SGPRs.
params[1].user_data.insert(params[1].user_data.begin(), 8u, 0u);
params[1].user_data[0] = static_cast<uint32_t>(regs.hs_regs.user_data_addr);
params[1].user_data[1] = static_cast<uint32_t>(regs.hs_regs.user_data_addr >> 32u);
input_info = {};
if (!ShaderGetStaticVertexInputInfo(regs.ls_regs.data_addr, regs.hs_user_sgpr,
regs.hs_regs.rsrc2.user_sgpr, sh, local, input_info[0])) {
EXIT("failed to prepare local shader program\n");
}
input_info[0].logical_stage = ShaderType::Local;
input_info[1].logical_stage = ShaderType::TessellationControl;
input_info[1].scratch_size_dwords = control.scratch_size_dwords;
input_info[2].logical_stage = ShaderType::TessellationEvaluation;
input_info[2].scratch_size_dwords = evaluation.scratch_size_dwords;
input_info[2].pa_cl_vs_out_cntl = sh.m_paClVsOutCntl;
ShaderTessellationInputInfo tess {
.input_control_points = (sh.m_vgtLsHsConfig >> 8u) & 0x3fu,
.output_control_points = (sh.m_vgtLsHsConfig >> 14u) & 0x3fu,
.domain = sh.m_vgtTfParam & 0x3u,
.partitioning = (sh.m_vgtTfParam >> 2u) & 0x3u,
.output_topology = (sh.m_vgtTfParam >> 5u) & 0x3u,
};
EXIT_IF(tess.input_control_points == 0 || tess.input_control_points > 32 ||
tess.output_control_points == 0 || tess.output_control_points > 32);
ShaderRecompiler::AnalyzeTessellationPrograms(params[0].code, params[1].code, tess);
for (auto& stage: input_info) {
stage.tess = tess;
}
return params;
}
ShaderParams PrepareProgram(
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
+24 -1
View File
@@ -33,7 +33,17 @@ struct ComputeShaderInfo;
struct ShaderRegisters;
} // namespace HW
enum class ShaderType { Unknown, Vertex, Pixel, Fetch, Compute, Mesh };
enum class ShaderType {
Unknown,
Vertex,
Pixel,
Fetch,
Compute,
Mesh,
Local,
TessellationControl,
TessellationEvaluation
};
namespace ShaderRecompiler::IR {
struct CompiledShaderInfo;
@@ -99,6 +109,16 @@ struct ShaderMeshInputInfo: ShaderWorkgroupInputInfo {
}
};
struct ShaderTessellationInputInfo {
uint32_t input_control_points = 0;
uint32_t output_control_points = 0;
uint32_t ls_stride = 0;
uint32_t hs_stride = 0;
uint32_t domain = 0;
uint32_t partitioning = 0;
uint32_t output_topology = 0;
};
struct ShaderVertexInputInfo {
static constexpr int RES_MAX = 32;
@@ -106,6 +126,7 @@ struct ShaderVertexInputInfo {
ShaderVertexDestination resources_dst[RES_MAX];
ShaderVertexInputBuffer buffers[RES_MAX];
ShaderStageRuntime stage;
ShaderType logical_stage = ShaderType::Vertex;
int resources_num = 0;
int fetch_attrib_reg = 0;
int fetch_buffer_reg = 0;
@@ -114,6 +135,7 @@ struct ShaderVertexInputInfo {
uint32_t pa_cl_vs_out_cntl = 0;
ShaderClipSpaceTransform clip_space;
ShaderMeshInputInfo mesh;
ShaderTessellationInputInfo tess;
bool fetch_external = false;
bool fetch_embedded = false;
};
@@ -131,6 +153,7 @@ struct ShaderComputeInputInfo: ShaderWorkgroupInputInfo {
struct ShaderPixelInputInfo {
uint32_t interpolator_settings[32] = {0};
uint32_t input_num = 0;
uint32_t wave_size = 64;
uint32_t ps_system_input_base = 0;
uint32_t custom_interpolation_mask = 0;
uint32_t ps_perspective_center_vgpr = UINT32_MAX;
+3
View File
@@ -30,6 +30,9 @@ void BuildStageStaticKey(const ShaderComputeInputInfo& input_info, std::vector<u
ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context& context,
const HW::UserConfig& user_config, ShaderVertexInputInfo& input_info);
std::array<ShaderParams, 3>
PrepareTessellationPrograms(const HW::VertexShaderInfo& regs, const HW::Context& context,
std::array<ShaderVertexInputInfo, 3>& input_info);
ShaderParams PrepareProgram(
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
+1
View File
@@ -458,6 +458,7 @@ int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode) {
file->real_name = g_mount_points->ResolvePath(file->name);
if (trunc && rw_mode == Common::File::Mode::Read) {
g_files->DeleteDescriptor(descriptor);
return KERNEL_ERROR_EACCES;
}
+19 -28
View File
@@ -346,7 +346,9 @@ public:
auto entry = std::prev(next);
if (vaddr + size <= entry->second.vaddr + entry->second.size) {
old = entry->second;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
m_maps.erase(entry);
#endif
}
}
}
@@ -355,6 +357,12 @@ public:
return false;
}
const uint64_t left_size = vaddr - old.vaddr;
const uint64_t right_addr = vaddr + size;
const uint64_t old_end = old.vaddr + old.size;
const uint64_t right_size = old_end - right_addr;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
const bool exact_unmap = (vaddr == old.vaddr && size == old.size);
if (exact_unmap) {
const bool unmapped =
@@ -370,12 +378,6 @@ public:
return true;
}
const uint64_t left_size = vaddr - old.vaddr;
const uint64_t right_addr = vaddr + size;
const uint64_t old_end = old.vaddr + old.size;
const uint64_t right_size = old_end - right_addr;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (UnmapViewPreservePlaceholder(reinterpret_cast<void*>(old.vaddr), old.size)) {
bool ok = true;
if (left_size != 0 && left_size != old.size) {
@@ -418,39 +420,28 @@ public:
m_maps.emplace(old.vaddr, old);
return false;
#else
if (!UnmapViewPreservePlaceholder(reinterpret_cast<void*>(old.vaddr), old.size)) {
std::lock_guard lock(m_mutex);
m_maps.emplace(old.vaddr, old);
// Replace only the requested range so concurrent accesses and GPU watch
// protections on the surviving mappings remain intact.
if (!UnmapViewPreservePlaceholder(reinterpret_cast<void*>(vaddr), size)) {
return false;
}
bool ok = true;
std::lock_guard lock(m_mutex);
m_maps.erase(old.vaddr);
if (left_size != 0) {
ok = MapExistingPlaceholderFixed(old.vaddr, left_size, old.backing_offset, old.mode) &&
ok;
m_maps.emplace(old.vaddr,
MapEntry {old.vaddr, left_size, old.backing_offset, old.mode});
}
if (right_size != 0) {
ok = MapExistingPlaceholderFixed(right_addr, right_size,
old.backing_offset + (right_addr - old.vaddr),
old.mode) &&
ok;
}
if (!ok) {
if (left_size != 0 && Contains(old.vaddr, left_size)) {
EXIT_IF(!UnmapOne(old.vaddr, left_size));
}
if (right_size != 0 && Contains(right_addr, right_size)) {
EXIT_IF(!UnmapOne(right_addr, right_size));
}
EXIT_IF(
!MapExistingPlaceholderFixed(old.vaddr, old.size, old.backing_offset, old.mode));
return false;
m_maps.emplace(right_addr,
MapEntry {right_addr, right_size,
old.backing_offset + right_addr - old.vaddr, old.mode});
}
if (placeholder_preserved != nullptr) {
*placeholder_preserved = true;
}
return ok;
return true;
#endif
}
-4
View File
@@ -3093,10 +3093,6 @@ Pthread PthreadSwapSelfForSignal(Pthread thread) {
return previous;
}
int PthreadGetUniqueId(Pthread thread) {
return thread != nullptr ? thread->unique_id : 0;
}
uint64_t PthreadGetHostThreadId(Pthread thread) {
return thread != nullptr ? thread->host_thread_id : 0;
}
-1
View File
@@ -110,7 +110,6 @@ int KYTY_SYSV_ABI PthreadRename(Pthread thread, const char* name);
void KYTY_SYSV_ABI PthreadYield();
int KYTY_SYSV_ABI PthreadGetthreadid();
int KYTY_SYSV_ABI PthreadGetaffinity(Pthread thread, KernelCpumask* mask);
int PthreadGetUniqueId(Pthread thread);
uint64_t PthreadGetHostThreadId(Pthread thread);
void PthreadWakeForSignal(Pthread thread);
void PthreadQueuePendingSignal(Pthread thread, int signum);
+20 -11
View File
@@ -47,11 +47,17 @@ struct WaitDeadline {
Clock::time_point end {};
};
[[nodiscard]] WaitDeadline MakeDeadline(std::chrono::nanoseconds timeout) {
const auto now = Clock::now();
const auto remaining = Clock::time_point::max() - now;
return {true, timeout >= remaining ? Clock::time_point::max() : now + timeout};
}
[[nodiscard]] WaitDeadline MakeDeadline(const uint32_t* timeout_micros) {
if (timeout_micros == nullptr) {
return {};
}
return {true, Clock::now() + std::chrono::microseconds(*timeout_micros)};
return MakeDeadline(std::chrono::microseconds(*timeout_micros));
}
[[nodiscard]] uint32_t GetWaitSliceMicros(const WaitDeadline& deadline, bool first_wait) {
@@ -65,7 +71,7 @@ struct WaitDeadline {
}
const auto remaining =
std::chrono::duration_cast<std::chrono::microseconds>(deadline.end - now).count();
std::chrono::ceil<std::chrono::microseconds>(deadline.end - now).count();
return static_cast<uint32_t>(std::min<int64_t>(remaining, SIGNAL_POLL_MICROS));
}
@@ -78,9 +84,8 @@ void PollSignals(signal_poll_func_t signal_poll) {
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
template <typename T>
int WaitLinux(volatile T* address, T expected, const uint32_t* timeout_micros,
int WaitLinux(volatile T* address, T expected, const WaitDeadline& deadline,
signal_poll_func_t signal_poll) {
const auto deadline = MakeDeadline(timeout_micros);
bool first_wait = true;
for (;;) {
@@ -185,11 +190,10 @@ void UnregisterPortableWaiter(volatile void* addre
}
template <typename T>
int WaitPortable(volatile T* address, T expected, const uint32_t* timeout_micros,
int WaitPortable(volatile T* address, T expected, const WaitDeadline& deadline,
signal_poll_func_t signal_poll) {
PortableWaiter waiter;
auto entry = RegisterPortableWaiter(address, &waiter);
const auto deadline = MakeDeadline(timeout_micros);
bool first_wait = true;
int result = OK;
@@ -250,7 +254,7 @@ int WakePortable(volatile void* address, int32_t count) {
}
template <typename T>
int WaitImpl(volatile T* address, T expected, const uint32_t* timeout_micros,
int WaitImpl(volatile T* address, T expected, const WaitDeadline& deadline,
signal_poll_func_t signal_poll) {
if (!IsValidWaitAddress(address)) {
return KERNEL_ERROR_EINVAL;
@@ -258,9 +262,9 @@ int WaitImpl(volatile T* address, T expected, const uint32_t* timeout_micros,
int result = OK;
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
result = WaitLinux(address, expected, timeout_micros, signal_poll);
result = WaitLinux(address, expected, deadline, signal_poll);
#else
result = WaitPortable(address, expected, timeout_micros, signal_poll);
result = WaitPortable(address, expected, deadline, signal_poll);
#endif
PollSignals(signal_poll);
return result;
@@ -270,12 +274,17 @@ int WaitImpl(volatile T* address, T expected, const uint32_t* timeout_micros,
int Wait32(volatile uint32_t* address, uint32_t expected, const uint32_t* timeout_micros,
signal_poll_func_t signal_poll) {
return WaitImpl(address, expected, timeout_micros, signal_poll);
return WaitImpl(address, expected, MakeDeadline(timeout_micros), signal_poll);
}
int Wait64(volatile uint64_t* address, uint64_t expected, const uint32_t* timeout_micros,
signal_poll_func_t signal_poll) {
return WaitImpl(address, expected, timeout_micros, signal_poll);
return WaitImpl(address, expected, MakeDeadline(timeout_micros), signal_poll);
}
int Wait64(volatile uint64_t* address, uint64_t expected, std::chrono::nanoseconds timeout,
signal_poll_func_t signal_poll) {
return WaitImpl(address, expected, MakeDeadline(timeout), signal_poll);
}
int Wake(volatile void* address, int32_t count) {
+4
View File
@@ -4,6 +4,8 @@
#include "common/abi.h"
#include "common/common.h"
#include <chrono>
namespace Libs::LibKernel::SyncOnAddress {
using signal_poll_func_t = void (*)();
@@ -12,6 +14,8 @@ int Wait32(volatile uint32_t* address, uint32_t expected, const uint32_t* timeou
signal_poll_func_t signal_poll = nullptr);
int Wait64(volatile uint64_t* address, uint64_t expected, const uint32_t* timeout_micros,
signal_poll_func_t signal_poll = nullptr);
int Wait64(volatile uint64_t* address, uint64_t expected, std::chrono::nanoseconds timeout,
signal_poll_func_t signal_poll = nullptr);
int Wake(volatile void* address, int32_t count);
} // namespace Libs::LibKernel::SyncOnAddress
@@ -218,6 +218,11 @@
<string>Firmware Version</string>
</property>
</column>
<column>
<property name="text">
<string>Size</string>
</property>
</column>
<column>
<property name="text">
<string>Path</string>
+36
View File
@@ -5,12 +5,15 @@
#include <QApplication>
#include <QComboBox>
#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <QFont>
#include <QFutureWatcher>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QLineEdit>
#include <QLocale>
#include <QSize>
#include <QStringList>
#include <QStyle>
@@ -18,6 +21,7 @@
#include <QVersionNumber>
#include <QWheelEvent>
#include <QWidget>
#include <QtConcurrentRun>
namespace {
@@ -26,6 +30,7 @@ enum Column {
SerialColumn,
GameVersionColumn,
FirmwareVersionColumn,
SizeColumn,
PathColumn,
StatusColumn,
CommentsColumn,
@@ -155,6 +160,34 @@ ConfigurationItem::ConfigurationItem(std::unique_ptr<Configuration> info, QTreeW
Update();
SetRunning(false);
setData(SizeColumn, Qt::UserRole, qint64(-1));
setText(SizeColumn, QStringLiteral("\u2014"));
auto* watcher = new QFutureWatcher<qint64>(this);
connect(watcher, &QFutureWatcher<qint64>::finished, this, [this, watcher]() {
const auto bytes = watcher->result();
setData(SizeColumn, Qt::UserRole, bytes);
if (bytes >= 0) {
setText(SizeColumn,
QLocale().formattedDataSize(bytes, 2, QLocale::DataSizeTraditionalFormat));
}
watcher->deleteLater();
});
watcher->setFuture(QtConcurrent::run([path = m_info->basedir]() -> qint64 {
if (path.isEmpty() || !QDir(path).exists()) {
return -1;
}
qint64 bytes = 0;
QDirIterator files(path, QDir::Files | QDir::Hidden | QDir::System | QDir::NoSymLinks,
QDirIterator::Subdirectories);
while (files.hasNext()) {
files.next();
if (files.fileInfo().isFile()) {
bytes += files.fileInfo().size();
}
}
return bytes;
}));
}
ConfigurationItem::~ConfigurationItem() = default;
@@ -193,6 +226,9 @@ bool ConfigurationItem::operator<(const QTreeWidgetItem& other) const {
const int column = treeWidget() != nullptr ? treeWidget()->sortColumn() : NameColumn;
switch (column) {
case NameColumn: return GetSortText(*m_info) < GetSortText(*other_item->m_info);
case SizeColumn:
return data(SizeColumn, Qt::UserRole).toLongLong() <
other.data(SizeColumn, Qt::UserRole).toLongLong();
case StatusColumn:
return GetStatusText(m_info->game_status) <
GetStatusText(other_item->m_info->game_status);
+5 -3
View File
@@ -61,9 +61,10 @@ constexpr int GAME_NAME_COLUMN = 0;
constexpr int GAME_SERIAL_COLUMN = 1;
constexpr int GAME_VERSION_COLUMN = 2;
constexpr int GAME_FIRMWARE_VERSION_COLUMN = 3;
constexpr int GAME_PATH_COLUMN = 4;
constexpr int GAME_STATUS_COLUMN = 5;
constexpr int GAME_COMMENT_COLUMN = 6;
constexpr int GAME_SIZE_COLUMN = 4;
constexpr int GAME_PATH_COLUMN = 5;
constexpr int GAME_STATUS_COLUMN = 6;
constexpr int GAME_COMMENT_COLUMN = 7;
static QString NormalizeGameDirectory(const QString& dir) {
const auto trimmed = dir.trimmed();
@@ -217,6 +218,7 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
m_ui->cfgs_list->setColumnWidth(GAME_SERIAL_COLUMN, 110);
m_ui->cfgs_list->setColumnWidth(GAME_VERSION_COLUMN, 120);
m_ui->cfgs_list->setColumnWidth(GAME_FIRMWARE_VERSION_COLUMN, 150);
m_ui->cfgs_list->setColumnWidth(GAME_SIZE_COLUMN, 100);
m_ui->cfgs_list->setColumnWidth(GAME_PATH_COLUMN, 320);
m_ui->cfgs_list->setColumnWidth(GAME_STATUS_COLUMN, 150);
m_ui->cfgs_list->setColumnWidth(GAME_COMMENT_COLUMN, 240);
+23 -3
View File
@@ -552,6 +552,7 @@ private:
struct ReadyFrame {
std::unique_ptr<GuestBuffer> buffer;
AvPlayerFrameInfoEx info {};
uint64_t timestamp_offset = 0;
};
class FileStreamer {
@@ -933,6 +934,7 @@ public:
}
current_video = std::move(*frame);
*out = current_video->info;
RecordLoopBoundary(*current_video);
if (deliver_seek_frame) {
seek_video_frame_pending = false;
}
@@ -963,11 +965,25 @@ public:
std::memcpy(out->details.audio.language_code,
current_audio->info.details.audio.language_code, 4);
last_audio_ts = out->time_stamp;
RecordLoopBoundary(*current_audio);
return true;
}
std::optional<int32_t> TakeWarning() { return warnings.TryPop(); }
std::optional<int32_t> TakeWarning() {
std::lock_guard lock(mutex);
if (pending_loop_warnings == 0) {
return std::nullopt;
}
--pending_loop_warnings;
return AVPLAYER_WARNING_LOOPING_BACK;
}
private:
void RecordLoopBoundary(const ReadyFrame& frame) {
if (frame.timestamp_offset > last_output_loop_offset) {
last_output_loop_offset = frame.timestamp_offset;
++pending_loop_warnings;
}
}
void Close() {
Stop();
if (fmt != nullptr) {
@@ -1014,6 +1030,8 @@ private:
audio_done = true;
seek_video_frame_pending = false;
last_audio_ts = 0;
last_output_loop_offset = 0;
pending_loop_warnings = 0;
current_video = std::move(delivered_video);
current_audio = std::move(delivered_audio);
retired_video = std::move(in_flight_video);
@@ -1220,7 +1238,6 @@ private:
if (avformat_seek_file(fmt, stream, INT64_MIN, 0, INT64_MAX, 0) < 0) {
break;
}
warnings.Push(AVPLAYER_WARNING_LOOPING_BACK);
continue;
}
if (result != AVERROR_EOF && !worker_stop) {
@@ -1340,6 +1357,7 @@ private:
return false;
}
ready.info.time_stamp += timestamp_offset;
ready.timestamp_offset = timestamp_offset;
frames.Push(std::move(ready));
av_frame_free(&frame);
}
@@ -1603,7 +1621,6 @@ private:
std::optional<ReadyFrame> current_video;
std::optional<ReadyFrame> current_audio;
std::deque<std::unique_ptr<GuestBuffer>> retired_video;
WorkQueue<int32_t> warnings;
LibKernel::Pthread demux_thread = nullptr;
LibKernel::Pthread video_thread = nullptr;
LibKernel::Pthread audio_thread = nullptr;
@@ -1623,6 +1640,8 @@ private:
uint32_t sync_mode = 0;
uint64_t start_time_ms = 0;
uint64_t last_audio_ts = 0;
uint64_t last_output_loop_offset = 0;
uint32_t pending_loop_warnings = 0;
std::chrono::steady_clock::time_point clock_start {};
std::chrono::steady_clock::time_point pause_time {};
std::chrono::steady_clock::duration paused_extra {};
@@ -1951,6 +1970,7 @@ Bool KYTY_SYSV_ABI AvPlayerGetVideoData(AvPlayerInternal* h, AvPlayerFrameInfo*
video_info->details.video.height = ex.details.video.height;
video_info->details.video.aspect_ratio = ex.details.video.aspect_ratio;
std::memcpy(video_info->details.video.language_code, ex.details.video.language_code, 4);
pump_warnings(h);
return 1;
}
Bool KYTY_SYSV_ABI AvPlayerGetVideoDataEx(AvPlayerInternal* h, AvPlayerFrameInfoEx* video_info) {
+24
View File
@@ -0,0 +1,24 @@
#ifndef EMULATOR_SRC_LIBS_HMD2_H_
#define EMULATOR_SRC_LIBS_HMD2_H_
#include <atomic>
#include <cstdint>
namespace Libs::Hmd2 {
inline constexpr uint32_t PANEL_WIDTH = 4000;
inline constexpr uint32_t PANEL_HEIGHT = 2040;
inline constexpr uint64_t OUTPUT_MODE_89_91HZ = 0x2000c;
inline constexpr uint64_t OUTPUT_MODE_119_88HZ = 0x2000f;
inline constexpr int32_t ERROR_REPROJECTION_NOT_INITIALIZED = -1972240373; // 0x8a72000b
struct ReprojectionState {
std::atomic<uint64_t> output_mode {0};
std::atomic<uint32_t> timing_us {3000};
};
[[nodiscard]] ReprojectionState* GetReprojectionState();
} // namespace Libs::Hmd2
#endif // EMULATOR_SRC_LIBS_HMD2_H_
+239
View File
@@ -0,0 +1,239 @@
#include "common/emulatorConfig.h"
#include "graphics/guest_gpu/tile.h"
#include "libs/agc.h"
#include "libs/errno.h"
#include "libs/hmd2.h"
#include "libs/libs.h"
#include "loader/symbolDatabase.h"
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <mutex>
#include <new>
namespace Libs {
LIB_VERSION("Hmd2", 1, "Hmd2", 1, 1);
namespace Hmd2 {
constexpr int32_t ERROR_ALREADY_INITIALIZED = -1972240383; // 0x8a720001
constexpr int32_t ERROR_NOT_INITIALIZED = -1972240382; // 0x8a720002
constexpr int32_t ERROR_PARAMETER_NULL = -1972240376; // 0x8a720008
constexpr int32_t ERROR_REPROJECTION_ALREADY_INITIALIZED = -1972240372; // 0x8a72000c
constexpr int32_t ERROR_REPROJECTION_IN_VR_MODE = -1972240356; // 0x8a72001c
struct SceHmd2InitializeParam {
void* reserved0;
uint8_t reserved[8];
};
struct SceHmd2DeviceInformation {
uint32_t status;
uint32_t reserve0;
struct {
struct {
uint32_t width;
uint32_t height;
} panelResolution;
struct {
uint16_t refreshRate90Hz;
uint16_t refreshRate120Hz;
} flipToDisplayLatency;
} deviceInfo;
uint8_t hmuMount;
uint8_t lensSeparationDistance;
uint8_t reserve1[2];
float virtualImageDistance;
};
struct SceHmd2FieldOfView {
float tanOut;
float tanIn;
float tanTop;
float tanBottom;
};
struct SceHmd2ReprojectionInitializeParam {
void* pReprojectionBuff;
void* pDisplayBuff;
int32_t threadPriority;
int32_t cpuAffinityMask;
uint32_t pipeId;
uint32_t queueId;
uint32_t type;
void* pSeeThroughBuff;
int32_t reprojectionTiming;
uint32_t reserved[5];
};
static_assert(sizeof(SceHmd2InitializeParam) == 16);
static_assert(offsetof(SceHmd2InitializeParam, reserved) == 8);
static_assert(sizeof(SceHmd2DeviceInformation) == 28);
static_assert(offsetof(SceHmd2DeviceInformation, deviceInfo) == 8);
static_assert(offsetof(SceHmd2DeviceInformation, hmuMount) == 20);
static_assert(offsetof(SceHmd2DeviceInformation, virtualImageDistance) == 24);
static_assert(sizeof(SceHmd2FieldOfView) == 16);
static_assert(alignof(SceHmd2FieldOfView) == 4);
static_assert(sizeof(SceHmd2ReprojectionInitializeParam) == 72);
static_assert(alignof(SceHmd2ReprojectionInitializeParam) == 8);
static_assert(offsetof(SceHmd2ReprojectionInitializeParam, threadPriority) == 16);
static_assert(offsetof(SceHmd2ReprojectionInitializeParam, type) == 32);
static_assert(offsetof(SceHmd2ReprojectionInitializeParam, pSeeThroughBuff) == 40);
static_assert(offsetof(SceHmd2ReprojectionInitializeParam, reprojectionTiming) == 48);
static_assert(offsetof(SceHmd2ReprojectionInitializeParam, reserved) == 52);
static std::atomic<bool> g_initialized = false;
static std::mutex g_reprojection_mutex;
static std::atomic<ReprojectionState*> g_reprojection_state {nullptr};
ReprojectionState* GetReprojectionState() {
return g_reprojection_state.load(std::memory_order_acquire);
}
static Graphics::SizeAlign ReprojectionDisplaySize() {
// The host display target is one uncompressed tiled 10-bit panel surface.
Graphics::TileSizeAlign size {};
Graphics::TileGetTextureTotalSize(Graphics::Prospero::BufferFormat::k10_10_10_2UNorm,
PANEL_WIDTH, PANEL_HEIGHT, 1, 1,
Graphics::Prospero::TileMode::kRenderTarget, false, size);
EXIT_IF(size.size == 0 || size.align == 0);
return {size.size, size.align};
}
static int32_t KYTY_SYSV_ABI Hmd2Initialize(const SceHmd2InitializeParam* param) {
PRINT_NAME();
if (param == nullptr) {
return ERROR_PARAMETER_NULL;
}
if (g_initialized.exchange(true)) {
return ERROR_ALREADY_INITIALIZED;
}
LOGF("Hmd2: initialized, virtual headset=%s\n", Config::VrEnabled() ? "enabled" : "disabled");
return OK;
}
static int32_t KYTY_SYSV_ABI Hmd2GetDeviceInformation(SceHmd2DeviceInformation* info) {
PRINT_NAME();
if (!g_initialized.load()) {
return ERROR_NOT_INITIALIZED;
}
if (info == nullptr) {
return ERROR_PARAMETER_NULL;
}
*info = {};
if (Config::VrEnabled()) {
// Virtual headset defaults: READY, MOUNT, 63 mm lens separation.
info->hmuMount = 1;
info->lensSeparationDistance = 63;
// Headset characteristics: 4000x2040 panel and 2 m virtual image distance.
info->deviceInfo.panelResolution = {PANEL_WIDTH, PANEL_HEIGHT};
info->virtualImageDistance = 2.0f;
// Host presentation has no emulated headset scan-out delay; latency remains zero.
} else {
info->status = 2; // SCE_HMD2_DEVICE_STATUS_NOT_DETECTED
}
LOGF("Hmd2: device status=%u panel=%ux%u mounted=%u\n", info->status,
info->deviceInfo.panelResolution.width, info->deviceInfo.panelResolution.height,
info->hmuMount);
return OK;
}
static int32_t KYTY_SYSV_ABI Hmd2GetFieldOfViewWithoutHandle(SceHmd2FieldOfView* fov) {
PRINT_NAME();
if (!g_initialized.load()) {
return ERROR_NOT_INITIALIZED;
}
if (fov == nullptr) {
return ERROR_PARAMETER_NULL;
}
// Virtual ER15 field of view: 55/42.5/51.2/51.2 degrees.
// These tangents describe that geometry, not bit-exact firmware output.
*fov = {1.4281480312f, 0.9163311720f, 1.2437491417f, 1.2437491417f};
LOGF("Hmd2: FOV tangents out=%f in=%f top=%f bottom=%f\n", fov->tanOut, fov->tanIn,
fov->tanTop, fov->tanBottom);
return OK;
}
static Graphics::SizeAlign KYTY_SYSV_ABI Hmd2ReprojectionQueryBufferSizeAlign() {
const Graphics::SizeAlign size {sizeof(ReprojectionState), alignof(ReprojectionState)};
LOGF("Hmd2: reprojection work size=%" PRIu64 " alignment=%zu\n", size.m_size, size.m_align);
return size;
}
static Graphics::SizeAlign KYTY_SYSV_ABI Hmd2ReprojectionQueryDisplayBufferSizeAlign() {
const auto size = ReprojectionDisplaySize();
LOGF("Hmd2: reprojection display size=%" PRIu64 " alignment=%zu\n", size.m_size,
size.m_align);
return size;
}
static int32_t KYTY_SYSV_ABI
Hmd2ReprojectionInitialize(const SceHmd2ReprojectionInitializeParam* param, void*) {
std::scoped_lock lock {g_reprojection_mutex};
if (GetReprojectionState() != nullptr) {
return ERROR_REPROJECTION_ALREADY_INITIALIZED;
}
if (param == nullptr || param->pReprojectionBuff == nullptr || param->pDisplayBuff == nullptr) {
return ERROR_PARAMETER_NULL;
}
EXIT_NOT_IMPLEMENTED(param->pSeeThroughBuff != nullptr);
const auto display_size = ReprojectionDisplaySize();
auto* state = new (param->pReprojectionBuff) ReprojectionState;
if (param->reprojectionTiming != 0) {
state->timing_us.store(static_cast<uint32_t>(param->reprojectionTiming),
std::memory_order_relaxed);
}
g_reprojection_state.store(state, std::memory_order_release);
LOGF("Hmd2: reprojection initialized work=%p display=%p size=%" PRIu64 " timing=%u\n",
static_cast<void*>(state), param->pDisplayBuff, display_size.m_size,
state->timing_us.load(std::memory_order_relaxed));
return OK;
}
static int32_t KYTY_SYSV_ABI Hmd2ReprojectionEnableVrMode(uint64_t output_mode) {
auto* state = GetReprojectionState();
if (state == nullptr) {
return ERROR_REPROJECTION_NOT_INITIALIZED;
}
if (output_mode != OUTPUT_MODE_89_91HZ && output_mode != OUTPUT_MODE_119_88HZ) {
return VideoOut::VIDEO_OUT_ERROR_INVALID_VALUE;
}
if (!Config::VrEnabled()) {
return VideoOut::VIDEO_OUT_ERROR_NO_DEVICE;
}
uint64_t expected = 0;
if (!state->output_mode.compare_exchange_strong(expected, output_mode)) {
return ERROR_REPROJECTION_IN_VR_MODE;
}
LOGF("Hmd2: VR output mode=0x%" PRIx64 "\n", output_mode);
return OK;
}
static int32_t KYTY_SYSV_ABI Hmd2ReprojectionSetTiming(int32_t timing) {
auto* state = GetReprojectionState();
if (state == nullptr) {
return ERROR_REPROJECTION_NOT_INITIALIZED;
}
EXIT_NOT_IMPLEMENTED(timing < 2000 || timing > 7000);
const auto rounded = static_cast<uint32_t>((timing + 99) / 100 * 100);
state->timing_us.store(rounded, std::memory_order_release);
LOGF("Hmd2: reprojection timing=%u us\n", rounded);
return OK;
}
} // namespace Hmd2
LIB_DEFINE(InitHmd2_1) {
LIB_FUNC("c812oYs7Vsc", Hmd2::Hmd2Initialize);
LIB_FUNC("bIi4YUfSRys", Hmd2::Hmd2GetDeviceInformation);
LIB_FUNC("gF8+lvc7GuQ", Hmd2::Hmd2GetFieldOfViewWithoutHandle);
LIB_FUNC("U-CnbmeyYaA", Hmd2::Hmd2ReprojectionQueryBufferSizeAlign);
LIB_FUNC("-C2nkoEYOnU", Hmd2::Hmd2ReprojectionQueryDisplayBufferSizeAlign);
LIB_FUNC("C0rPwER-yxg", Hmd2::Hmd2ReprojectionInitialize);
LIB_FUNC("VVvFh51o20s", Hmd2::Hmd2ReprojectionEnableVrMode);
LIB_FUNC("FkQX7rjFomk", Hmd2::Hmd2ReprojectionSetTiming);
}
} // namespace Libs
+16
View File
@@ -1006,6 +1006,21 @@ static int32_t KYTY_SYSV_ABI JsonValueCount(const JsonValue* self) {
}
}
static void KYTY_SYSV_ABI JsonValueClearMethod(void* self) {
PRINT_NAME();
LOGF("\t self = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(self));
auto* value = reinterpret_cast<JsonValue*>(self);
if (value != nullptr) {
auto* parent = value->parent;
auto* rootparam = value->rootparam;
JsonValueClear(value);
value->parent = parent;
value->rootparam = rootparam;
}
}
LIB_DEFINE(InitNet_1_Json2) {
LIB_FUNC("-hJRce8wn1U", LibJson2::JsonMemAllocatorCtor);
LIB_FUNC("WSOuge5IsCg", LibJson2::JsonInitParameter2Ctor);
@@ -1076,6 +1091,7 @@ LIB_DEFINE(InitNet_1_Json2) {
LIB_FUNC("RBw+4NukeGQ", LibJson2::JsonValueCount);
LIB_FUNC("+drDFyAS6u4", LibJson2::JsonInitializerSetGlobalNullAccessCallback);
LIB_FUNC("00oCq0RwSAY", LibJson2::JsonInitializerSetGlobalNullAccessCallback);
LIB_FUNC("FIjXN2TkuTs", LibJson2::JsonValueClearMethod);
}
} // namespace LibJson2
+7 -3
View File
@@ -1168,7 +1168,6 @@ static int KYTY_SYSV_ABI KernelRaiseException(Pthread thread, int signum) {
return KERNEL_ERROR_EINVAL;
}
Common::CondVar::SignalThread(PthreadGetUniqueId(thread));
PthreadWakeForSignal(thread);
CloseHandle(target_thread);
return OK;
@@ -1193,7 +1192,6 @@ static int KYTY_SYSV_ABI KernelRaiseException(Pthread thread, int signum) {
return KERNEL_ERROR_EINVAL;
}
Common::CondVar::SignalThread(PthreadGetUniqueId(thread));
PthreadWakeForSignal(thread);
WaitForSignalDispatch(thread, signum);
return OK;
@@ -2128,7 +2126,13 @@ int KYTY_SYSV_ABI UmtxOp(volatile void* address, int operation, uint64_t value,
*GetErrorAddr() = POSIX_EINVAL;
return -1;
}
EXIT("Valid _umtx_op timed waits are not implemented\n");
const auto max_ns = std::chrono::nanoseconds::max().count();
const auto timeout_ns = duration.tv_sec > (max_ns - duration.tv_nsec) / 1000000000
? max_ns
: duration.tv_sec * 1000000000 + duration.tv_nsec;
return POSIX_CALL(LibKernel::SyncOnAddress::Wait64(
static_cast<volatile uint64_t*>(address), value, std::chrono::nanoseconds(timeout_ns),
LibKernel::KernelDispatchPendingSignalForCurrentThread));
}
return POSIX_CALL(LibKernel::SyncOnAddress::Wait64(
static_cast<volatile uint64_t*>(address), value, nullptr,
+2
View File
@@ -82,6 +82,7 @@ LIB_DEFINE(InitDialog_1);
LIB_DEFINE(InitFont_1);
LIB_DEFINE(InitFontFt_1);
LIB_DEFINE(InitAgcDriver_1);
LIB_DEFINE(InitHmd2_1);
LIB_DEFINE(InitLibKernel_1);
LIB_DEFINE(InitNet_1);
LIB_DEFINE(InitPad_1);
@@ -115,6 +116,7 @@ void InitAll(Loader::SymbolDatabase* s) {
InitFont_1(s);
InitFontFt_1(s);
InitAgcDriver_1(s);
InitHmd2_1(s);
InitLibKernel_1(s);
LibMouse::InitMouse_1(s);
LibKeyboard::InitKeyboard_1(s);
+20 -12
View File
@@ -14,6 +14,7 @@
#endif
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <sys/select.h>
@@ -1296,7 +1297,6 @@ int KYTY_SYSV_ABI NetResolverStartNtoa(int rid, const char* hostname, void* addr
return OK;
}
#if defined(_WIN32)
if (!EnsureSocketBackend()) {
return NET_ERROR_ENETDOWN;
}
@@ -1311,7 +1311,13 @@ int KYTY_SYSV_ABI NetResolverStartNtoa(int rid, const char* hostname, void* addr
if (result != nullptr) {
freeaddrinfo(result);
}
return ret == EAI_NONAME ? NET_ERROR_RESOLVER_ENOHOST : NET_ERROR_RESOLVER_EINTERNAL;
if (ret == EAI_NONAME) {
return NET_ERROR_RESOLVER_ENOHOST;
}
if (ret == EAI_NODATA) {
return NET_ERROR_RESOLVER_ENORECORD;
}
return NET_ERROR_RESOLVER_EINTERNAL;
}
for (auto* ai = result; ai != nullptr; ai = ai->ai_next) {
@@ -1326,9 +1332,6 @@ int KYTY_SYSV_ABI NetResolverStartNtoa(int rid, const char* hostname, void* addr
freeaddrinfo(result);
return NET_ERROR_RESOLVER_ENORECORD;
#else
return NET_ERROR_RESOLVER_ENOTIMPLEMENTED;
#endif
}
int KYTY_SYSV_ABI NetInetPton(int af, const char* src, void* dst) {
@@ -2025,22 +2028,27 @@ int64_t KYTY_SYSV_ABI Recvfrom(int s, void* buf, uint64_t len, int flags, void*
const auto host_len = static_cast<SocketIoLength>(
std::min<uint64_t>(len, std::numeric_limits<SocketIoLength>::max()));
int64_t result = 0;
sockaddr_storage host_addr {};
SocketLength host_addrlen = sizeof(host_addr);
int64_t result = 0;
if (addr == nullptr) {
result = ::recv(socket, static_cast<char*>(buf), host_len, host_flags);
} else {
sockaddr_storage host_addr {};
SocketLength host_addrlen = sizeof(host_addr);
result = ::recvfrom(socket, static_cast<char*>(buf), host_len, host_flags,
reinterpret_cast<sockaddr*>(&host_addr), &host_addrlen);
if (result >= 0 &&
ConvertHostSockaddr(&host_addr, host_addrlen, addr, addrlen) != 0) {
return -1;
}
}
#if defined(_WIN32)
if (result < 0 && WSAGetLastError() == WSAEMSGSIZE) {
// Winsock copied the datagram prefix; POSIX reports its length as success.
result = host_len;
}
#endif
if (result < 0) {
return SetHostSocketError();
}
if (addr != nullptr && ConvertHostSockaddr(&host_addr, host_addrlen, addr, addrlen) != 0) {
return -1;
}
return result;
}
+6
View File
@@ -54,6 +54,7 @@ static void PrintUsage() {
::printf(
" --gpu <index> Vulkan physical device index. Default: auto.\n");
::printf(" --fullscreen Run in borderless desktop fullscreen.\n");
::printf(" --vr Enable the virtual VR headset.\n");
::printf(" --vblank-frequency <num> Virtual vblank frequency. Default: 60.\n");
::printf(" --console-language <0-29> Console language. Default: 1 (English US).\n");
::printf(" --vulkan-validation <true|false> Enable Vulkan validation.\n");
@@ -161,6 +162,11 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
continue;
}
if (arg == "--vr") {
options.config.vr_enabled = true;
continue;
}
if (arg == "--playgo-hack") {
options.config.playgo_hack_enabled = true;
continue;
File diff suppressed because it is too large. Load diff
+33
View File
@@ -110,6 +110,38 @@ void TestValueChangeAndWake() {
Check(result == OK, "a value change plus wake releases the waiter");
}
void TestNanosecondTimeout() {
uint64_t word = 0;
const auto start = std::chrono::steady_clock::now();
Check(Wait64(&word, 0, std::chrono::milliseconds(1)) ==
Libs::LibKernel::KERNEL_ERROR_ETIMEDOUT,
"64-bit timespec wait expires");
Check(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(1),
"timespec wait preserves its deadline");
Check(Wait64(&word, 0, std::chrono::nanoseconds(0)) ==
Libs::LibKernel::KERNEL_ERROR_ETIMEDOUT,
"zero timespec polls a matching value");
Check(Wait64(&word, 1, std::chrono::nanoseconds(0)) == OK,
"zero timespec succeeds on mismatch");
g_signal_poll_count.store(0, std::memory_order_relaxed);
std::atomic<bool> returned{false};
std::thread waker([&] {
while (g_signal_poll_count.load(std::memory_order_relaxed) < 2 &&
!returned.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
Store(&word, UINT64_C(0x100000000));
Check(Wake(&word, 1) == OK, "wake releases a long timespec wait");
});
const int result = Wait64(&word, 0, std::chrono::nanoseconds::max(), CountSignalPoll);
returned.store(true, std::memory_order_release);
waker.join();
Check(result == OK, "long timespec wait does not overflow its deadline");
Check(g_signal_poll_count.load(std::memory_order_relaxed) > 1,
"timespec wait continues polling guest signals");
}
void TestWakeOneThenAll() {
constexpr int WAITER_COUNT = 4;
uint32_t word = 0;
@@ -260,6 +292,7 @@ int main() {
TestMismatchReturnsImmediately();
TestTimeout();
TestValueChangeAndWake();
TestNanosecondTimeout();
TestWakeOneThenAll();
TestAddressesAreIsolated();
TestCompareRegisterWakeRace();
+45
View File
@@ -20,6 +20,11 @@
#include <string>
#include <vector>
#if defined(__linux__)
#include <sys/uio.h>
#include <unistd.h>
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#ifndef NOMINMAX
#define NOMINMAX
@@ -1163,6 +1168,43 @@ void TestDirectPartialProtectUnmapPreservesNeighbors() {
std::printf("[host] %-48s ok\n", test);
}
#if defined(__linux__)
void TestPartialUnmapPreservesHostPermissions() {
const char* test = "PartialUnmapPreservesHostPermissions";
const auto base = MapNamedFlexible(test, SceKernelPageSize * 3, SceKernelProtCpuRw,
"unmap_host_permissions");
using Common::VirtualMemory::Mode;
Check(test, Libs::LibKernel::Memory::ProtectGuestHostMemory(base, SceKernelPageSize, Mode::Read),
"could not protect the left survivor from writes");
Check(test,
Libs::LibKernel::Memory::ProtectGuestHostMemory(base + SceKernelPageSize * 2,
SceKernelPageSize, Mode::NoAccess),
"could not protect the right survivor from reads");
CheckOk(test,
Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize, SceKernelPageSize),
"KernelMunmap(middle)");
// Probe actual host access without taking a signal or changing the page protections.
uint64_t value = 0;
iovec local {&value, sizeof(value)};
iovec left {reinterpret_cast<void*>(base), sizeof(value)};
iovec right {reinterpret_cast<void*>(base + SceKernelPageSize * 2), sizeof(value)};
Check(test, process_vm_readv(getpid(), &local, 1, &left, 1, 0) == sizeof(value),
"partial unmap removed read access to the left survivor");
Check(test, process_vm_writev(getpid(), &local, 1, &left, 1, 0) == -1,
"partial unmap removed the left survivor's write protection");
Check(test, process_vm_readv(getpid(), &local, 1, &right, 1, 0) == -1,
"partial unmap removed the right survivor's read protection");
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize),
"KernelMunmap(left cleanup)");
CheckOk(test,
Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize * 2, SceKernelPageSize),
"KernelMunmap(right cleanup)");
std::printf("[host] %-48s ok\n", test);
}
#endif
void TestDirectMapValidationBeforeOwnerMutation() {
const char* test = "DirectMapValidationBeforeOwnerMutation";
int64_t invalid = -1;
@@ -2529,6 +2571,9 @@ int main(int argc, char** argv) {
RunTest(TestMunmapAcrossAdjacentFlexibleMappings);
RunTest(TestDirectMapQueryOffsetAndPartialMunmap);
RunTest(TestDirectPartialProtectUnmapPreservesNeighbors);
#if defined(__linux__)
RunTest(TestPartialUnmapPreservesHostPermissions);
#endif
RunTest(TestDirectMapValidationBeforeOwnerMutation);
RunTest(TestDirectReleaseRollbackRestoresOwnerMapping);
RunTest(TestDirectReleaseContracts);
+271 -32
View File
@@ -1183,37 +1183,90 @@ void TestNativeShaderResourceDependencies() {
}
void TestNormalizedImageContracts() {
// Captured PS5 256x256 atlas: each layer is a 0x56000-byte mip chain.
ImageInfo container{};
container.data = {0x10000, 0x15000};
container.data = {0x241741000, 0x2198000};
container.pixel_format = vk::Format::eR8G8B8A8Unorm;
container.guest_format = Prospero::BufferFormat::k8_8_8_8UNorm;
container.type = Prospero::ImageType::kColor2D;
container.extent = {64, 64, 1};
container.resources = {3, 4};
container.pitch = 64;
container.extent = {256, 256, 1};
container.resources = {9, 100};
container.pitch = 256;
container.bytes_per_block = 4;
container.samples = 1;
container.tile_mode = Prospero::TileMode::kStandard64KB;
container.mip_layout[0] = {0, 0x10000, 64, 64};
container.mip_layout[1] = {0x10000, 0x4000, 32, 32};
container.mip_layout[2] = {0x14000, 0x1000, 16, 16};
container.tile_mode = Prospero::TileMode::kStandard4KB;
container.mip_layout[0] = {0x16000, 0x1900000, 256, 256};
container.mip_layout[1] = {0x6000, 0x640000, 128, 128};
container.mip_layout[2] = {0x2000, 0x190000, 64, 64};
container.mip_layout[3] = {0x1000, 0x64000, 32, 32};
for (uint32_t level = 4; level < container.resources.levels; ++level) {
container.mip_layout[level] = {0, 0x64000, 32, 32};
}
ImageInfo subresource = container;
subresource.data = {0x22000, 0x1000};
subresource.extent = {32, 32, 1};
subresource.data = {0x2417ad000, 0x40000};
subresource.resources = {1, 1};
subresource.pitch = 32;
subresource.mip_layout = {};
subresource.mip_layout[0] = {0, 0x1000, 32, 32};
subresource.mip_layout[0] = {0, 0x40000, 256, 256};
Check(container.BlockExtent() == vk::Extent2D{64, 64},
Check(container.BlockExtent() == vk::Extent2D{256, 256},
"normalized image block extent changed");
Check(subresource.IsCompatible(container),
"normalized compatible image was rejected");
Check(subresource.MipOf(container) == 1,
"normalized mip lookup missed a subresource");
Check(subresource.SliceOf(container, 1) == 2,
"normalized slice lookup missed a subresource");
Check(subresource.MipOf(container) == 0 &&
subresource.SliceOf(container, 0) == 1,
"PS5 mip-chain stride did not resolve the captured second array layer");
auto smaller = subresource;
smaller.data = {0x241747000, 0x10000};
smaller.extent = {128, 128, 1};
smaller.pitch = 128;
smaller.mip_layout[0] = {0, 0x10000, 128, 128};
Check(smaller.MipOf(container) == 1 && smaller.SliceOf(container, 1) == 0,
"PS5 reversed mip ordering did not resolve the captured smaller mip");
auto boundary = subresource;
boundary.data.address = container.data.address + 99 * 0x56000 + 0x16000;
Check(boundary.SliceOf(container, 0) == 99,
"PS5 array lookup rejected the final complete layer");
boundary.data.address += 0x56000;
Check(boundary.MipOf(container) == -1,
"PS5 array lookup accepted a layer beyond the parent range");
boundary = subresource;
boundary.data.address += 0x1000;
Check(boundary.MipOf(container) == -1,
"PS5 array lookup accepted a misaligned layer address");
boundary = subresource;
boundary.resources.layers = 2;
boundary.data.size *= 2;
boundary.mip_layout[0].size *= 2;
Check(boundary.MipOf(container) == -1,
"PS5 array lookup accepted contiguous child layers with a different stride");
boundary = subresource;
boundary.mip_layout[0].pitch *= 2;
Check(boundary.MipOf(container) == -1,
"PS5 array lookup accepted a different storage pitch");
auto small_atlas = container;
small_atlas.data = {0x239001000, 0x64000};
small_atlas.extent = {32, 32, 1};
small_atlas.pitch = 32;
small_atlas.resources = {6, 50};
small_atlas.mip_layout[0] = {0x1000, 0x32000, 32, 32};
for (uint32_t level = 1; level < small_atlas.resources.levels; ++level) {
small_atlas.mip_layout[level] = {0, 0x32000, 32, 32};
}
auto small_child = subresource;
small_child.data = {0x23900e000, 0x1000};
small_child.extent = {32, 32, 1};
small_child.pitch = 32;
small_child.mip_layout[0] = {0, 0x1000, 32, 32};
Check(small_child.MipOf(small_atlas) == 0 &&
small_child.SliceOf(small_atlas, 0) == 6,
"PS5 array lookup doubled the captured small-atlas layer index");
small_child.data.address = 0x23900d000;
Check(small_child.MipOf(small_atlas) == -1,
"PS5 array lookup treated a packed tail block as the full-size mip");
auto incompatible = subresource;
incompatible.samples = 2;
@@ -4170,6 +4223,24 @@ void TestNewShaderDecoderArchitecture() {
!sin_f16.src0.absolute,
"decoder rejected the VOP1 SDWA V_SIN_F16 instruction");
std::vector<uint32_t> fract_f16_code(0x9b0u / 4u, EncodeSopp(0x00));
fract_f16_code.push_back(0x7e12bf07u);
fract_f16_code.push_back(EncodeSopp(0x01));
Instruction fract_f16;
ShaderRecompiler::Decoder::DecodeInstruction(fract_f16_code, 0x9b0u / 4u,
fract_f16);
Check(fract_f16.pc == 0x9b0u && fract_f16.family == Family::VOP1 &&
fract_f16.opcode == Opcode::V_FRACT_F16 &&
fract_f16.opcode_id == 0x5fu && fract_f16.word_count == 1u &&
fract_f16.dst.kind == OperandKind::Vgpr && fract_f16.dst.reg == 9u &&
fract_f16.src_count == 1u &&
fract_f16.src0.kind == OperandKind::Vgpr && fract_f16.src0.reg == 7u,
"decoder rejected captured V_FRACT_F16 at pc 0x9b0");
ShaderRecompiler::Decoder::Program fract_program;
ShaderRecompiler::Decoder::DecodeProgram(fract_f16_code, fract_program);
Check(!ShaderRecompiler::CFG::BuildGraph(fract_program).unsupported,
"captured V_FRACT_F16 still fails CFG construction");
const uint32_t literal_code[] = {EncodeVop1(0x01, 2, 255u), 0x12345678u};
Instruction literal;
ShaderRecompiler::Decoder::DecodeInstruction(literal_code, 0u, literal);
@@ -4416,6 +4487,62 @@ void TestNewShaderRecompilerCapturedVopcSdwaCmpxClass() {
CheckSpirvBinaryValidates(result.spirv);
}
void TestNewShaderRecompilerCapturedVopcSdwaCmpxLtU16() {
using namespace ShaderRecompiler;
const uint32_t shader[] = {
0x7d72d4f9u, 0x86060000u, // v_cmpx_lt_u16 v0, vcc_lo (SDWA)
EncodeSopp(0x01),
};
Decoder::Instruction decoded;
Decoder::DecodeInstruction(shader, 0u, decoded);
Check(decoded.family == Decoder::Family::VOPC &&
decoded.opcode == Decoder::Opcode::V_CMPX_LT_U16 &&
decoded.opcode_id == 0xb9u && decoded.word_count == 2u &&
decoded.raw[0] == shader[0] && decoded.raw[1] == shader[1] &&
decoded.dst.kind == Decoder::OperandKind::ExecLo &&
decoded.src_count == 2u &&
decoded.src0.kind == Decoder::OperandKind::Vgpr &&
decoded.src0.reg == 0u && decoded.src0.sdwa_sel == 6u &&
decoded.src1.kind == Decoder::OperandKind::VccLo &&
decoded.src1.sdwa_sel == 6u &&
!decoded.src0.sdwa_sext && !decoded.src1.sdwa_sext &&
!decoded.src0.negate && !decoded.src1.negate &&
!decoded.src0.absolute && !decoded.src1.absolute,
"decoder rejected captured SDWA V_CMPX_LT_U16 fields");
Decoder::Program program;
Decoder::DecodeProgram(shader, program);
Check(!CFG::BuildGraph(program).unsupported,
"captured SDWA V_CMPX_LT_U16 still fails CFG construction");
auto result = RecompileForTest(shader, MakeCompileOptions(ShaderType::Pixel));
Check(Common::ContainsStr(result.decoded_dump,
"V_CMPX_LT_U16 exec_lo, v0, vcc_lo"),
"captured SDWA V_CMPX_LT_U16 is missing from decoded dump");
CheckSpirvBinaryValidates(result.spirv);
// Independently assembled gfx1030 compact and VOP3 encodings.
const uint32_t compact[] = {0x7d720300u};
const uint32_t vop3[] = {0xd4b9007eu, 0x00020300u};
for (auto words : {std::span<const uint32_t>(compact),
std::span<const uint32_t>(vop3)}) {
Decoder::DecodeInstruction(words, 0u, decoded);
Check(decoded.opcode == Decoder::Opcode::V_CMPX_LT_U16 &&
decoded.dst.kind == Decoder::OperandKind::ExecLo &&
decoded.src0.kind == Decoder::OperandKind::Vgpr &&
decoded.src0.reg == 0u &&
decoded.src1.kind == Decoder::OperandKind::Vgpr &&
decoded.src1.reg == 1u,
"compact/VOP3 V_CMPX_LT_U16 does not decode to an EXEC compare");
}
const uint32_t dpp[] = {EncodeVopc(0xb9u, 250u, 1u), EncodeVop2Dpp(0u)};
Decoder::DecodeInstruction(dpp, 0u, decoded);
Check(decoded.opcode == Decoder::Opcode::UNSUPPORTED &&
Common::ContainsStr(decoded.unsupported_reason,
"VOPC DPP modifier is not supported for opcode"),
"V_CMPX_LT_U16 accepted an unsupported DPP encoding");
}
void TestNewShaderRecompilerIrLookupMissFailsExplicitly() {
namespace Decoder = ShaderRecompiler::Decoder;
namespace CFG = ShaderRecompiler::CFG;
@@ -10025,11 +10152,120 @@ void TestNewShaderRecompilerAuxPositionExports() {
"PA_CL_VS_OUT_CNTL is absent from the vertex shader cache key");
}
void TestTypedSpirvSerialization() {
// Parameters exercise runtime enums, which cannot narrow implicitly to a
// uint32_t initializer list when the enum uses the Windows signed ABI.
const auto check_serialization = [](spv::StorageClass storage_class,
spv::FunctionControlMask control,
spv::Decoration decoration,
GLSLstd450 glsl_opcode,
int32_t signed_literal,
uint32_t high_bit_literal) {
ShaderRecompiler::Spirv::Builder builder;
builder.RequireCapability(spv::CapabilityShader);
const auto glsl = builder.Import("GLSL.std.450");
builder.AddMemoryModel(spv::AddressingModelLogical, spv::MemoryModelGLSL450);
const auto void_type = builder.Type(spv::OpTypeVoid);
const std::vector<uint32_t> uint_operands{32u, 0u};
const auto uint_type = builder.Type(spv::OpTypeInt, uint_operands);
Check(uint_type == builder.Type(spv::OpTypeInt, 32u, 0u) &&
uint_type == builder.Type(spv::OpTypeInt,
std::span<const uint32_t>{uint_operands}),
"typed, vector and span type operands produced different declarations");
const auto float_type = builder.Type(spv::OpTypeFloat, 32u);
const auto vector_type = builder.Type(spv::OpTypeVector, uint_type, 2u);
const auto pointer_type =
builder.Type(spv::OpTypePointer, storage_class, uint_type);
const auto function_type = builder.Type(spv::OpTypeFunction, void_type);
const auto all_bits =
builder.Constant(spv::OpConstant, uint_type, signed_literal);
Check(all_bits == builder.Constant(spv::OpConstant, uint_type, UINT32_MAX),
"signed and unsigned constant operands produced different declarations");
const auto high_bit =
builder.Constant(spv::OpConstant, uint_type, high_bit_literal);
const auto one = builder.Constant(spv::OpConstant, float_type, 0x3f800000u);
const std::vector<uint32_t> components{all_bits, high_bit};
const auto constant_vector = builder.Constant(
spv::OpConstantComposite, vector_type,
std::span<const uint32_t>{components});
Check(constant_vector == builder.Constant(spv::OpConstantComposite,
vector_type, components),
"vector and span constant operands produced different declarations");
const auto variable =
builder.DefineGlobalVariable(pointer_type, storage_class);
const auto function = builder.AllocateId();
const auto label = builder.AllocateId();
const auto absolute = builder.AllocateId();
const auto constructed = builder.AllocateId();
const auto shuffled = builder.AllocateId();
const auto loaded = builder.AllocateId();
builder.AddEntryPoint(spv::ExecutionModelGLCompute, function, "main", {});
builder.AddExecutionMode(function, spv::ExecutionModeLocalSize, 1u, 2u, 3u);
builder.AddAnnotation(spv::OpDecorate, one, decoration);
builder.AddFunction(spv::OpFunction, void_type, function, control,
function_type);
const std::vector<uint32_t> label_words{248u, label};
builder.AddFunction(label_words);
builder.AddFunction(spv::OpExtInst, float_type, absolute, glsl, glsl_opcode,
one);
builder.AddFunction(spv::OpCompositeConstruct, vector_type, constructed,
components);
const std::array<uint32_t, 2> selectors{1u, 2u};
builder.AddFunction(spv::OpVectorShuffle, vector_type, shuffled,
constant_vector, constructed,
std::span<const uint32_t>{selectors});
builder.AddFunction(spv::OpLoad, uint_type, loaded, variable,
spv::MemoryAccessAlignedMask, 4u);
builder.AddFunction(std::vector<uint32_t>{});
const std::array<uint32_t, 1> return_words{253u};
builder.AddFunction(std::span<const uint32_t>{return_words});
builder.AddFunction(spv::OpFunctionEnd);
// Literal headers and enum values keep this independent of the builder's
// instruction word counts, operand conversion and range flattening.
const std::vector<uint32_t> expected{
0x07230203u, 0x00010300u, 0u, loaded + 1u, 0u,
0x00020011u, 1u, // OpCapability Shader
0x0006000bu, glsl, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0u,
0x0003000eu, 0u, 1u, // OpMemoryModel Logical GLSL450
0x0005000fu, 5u, function, 0x6e69616du, 0u,
0x00060010u, function, 17u, 1u, 2u, 3u,
0x00030047u, one, 0u, // OpDecorate RelaxedPrecision
0x00020013u, void_type,
0x00040015u, uint_type, 32u, 0u,
0x00030016u, float_type, 32u,
0x00040017u, vector_type, uint_type, 2u,
0x00040020u, pointer_type, 6u, uint_type,
0x00030021u, function_type, void_type,
0x0004002bu, uint_type, all_bits, 0xffffffffu,
0x0004002bu, uint_type, high_bit, 0x80000000u,
0x0004002bu, float_type, one, 0x3f800000u,
0x0005002cu, vector_type, constant_vector, all_bits, high_bit,
0x0004003bu, pointer_type, variable, 6u,
0x00050036u, void_type, function, 0u, function_type,
0x000200f8u, label,
0x0006000cu, float_type, absolute, glsl, 4u, one,
0x00050050u, vector_type, constructed, all_bits, high_bit,
0x0007004fu, vector_type, shuffled, constant_vector, constructed, 1u, 2u,
0x0006003du, uint_type, loaded, variable, 2u, 4u,
0x000100fdu,
0x00010038u,
};
const auto binary = builder.Build();
Check(binary == expected,
"typed SPIR-V serialization changed instruction words or operand order");
CheckSpirvBinaryValidates(binary);
};
check_serialization(spv::StorageClassPrivate, spv::FunctionControlMaskNone,
spv::DecorationRelaxedPrecision, GLSLstd450FAbs, -1,
0x80000000u);
}
void TestDeferredSpirvPhiPatching() {
ShaderRecompiler::Spirv::Builder builder;
const auto type = builder.Type(spv::OpTypeInt, {32u, 0u});
const auto zero = builder.Constant(spv::OpConstant, type, {0u});
const auto one = builder.Constant(spv::OpConstant, type, {1u});
const auto type = builder.Type(spv::OpTypeInt, 32u, 0u);
const auto zero = builder.Constant(spv::OpConstant, type, 0u);
const auto one = builder.Constant(spv::OpConstant, type, 1u);
const auto left = builder.AllocateId();
const auto right = builder.AllocateId();
const auto result = builder.AllocateId();
@@ -10080,35 +10316,36 @@ void TestDemandDrivenSpirvDeclarations() {
using ShaderRecompiler::Spirv::Builder;
Builder declarations;
const auto uint_type = declarations.Type(spv::OpTypeInt, {32u, 0u});
Check(uint_type == declarations.Type(spv::OpTypeInt, {32u, 0u}),
const auto uint_type = declarations.Type(spv::OpTypeInt, 32u, 0u);
Check(uint_type == declarations.Type(spv::OpTypeInt, 32u, 0u),
"structural type interning returned different IDs");
const auto zero = declarations.Constant(spv::OpConstant, uint_type, {0u});
Check(zero == declarations.Constant(spv::OpConstant, uint_type, {0u}),
const auto zero = declarations.Constant(spv::OpConstant, uint_type, 0u);
Check(zero == declarations.Constant(spv::OpConstant, uint_type, 0u),
"constant interning returned different IDs");
Check(declarations.Import("GLSL.std.450") ==
declarations.Import("GLSL.std.450"),
"extended-instruction import was duplicated");
declarations.RequireCapability(spv::CapabilityShader);
declarations.RequireCapability(spv::CapabilityShader);
const auto plain_array =
declarations.Type(spv::OpTypeArray, {uint_type, zero});
const auto plain_array = declarations.Type(spv::OpTypeArray, uint_type, zero);
const auto decorated_array = declarations.DecoratedType(
spv::OpTypeArray, {uint_type, zero},
{{spv::OpDecorate, {spv::DecorationArrayStride, sizeof(uint32_t)}}});
spv::OpTypeArray,
{{spv::OpDecorate, {spv::DecorationArrayStride, sizeof(uint32_t)}}},
uint_type, zero);
Check(decorated_array ==
declarations.DecoratedType(
spv::OpTypeArray, {uint_type, zero},
spv::OpTypeArray,
{{spv::OpDecorate,
{spv::DecorationArrayStride, sizeof(uint32_t)}}}),
{spv::DecorationArrayStride, sizeof(uint32_t)}}},
uint_type, zero),
"decorated type interning returned different IDs");
Check(plain_array != decorated_array,
"decorated and undecorated aggregate types were aliased");
Check(declarations.DecoratedType(spv::OpTypeArray, {uint_type, zero}, {}) ==
Check(declarations.DecoratedType(spv::OpTypeArray, {}, uint_type, zero) ==
plain_array,
"empty decorated type request bypassed structural interning");
const auto ptr = declarations.Type(spv::OpTypePointer,
{spv::StorageClassFunction, uint_type});
spv::StorageClassFunction, uint_type);
Check(declarations.DefineGlobalVariable(ptr, spv::StorageClassFunction) !=
declarations.DefineGlobalVariable(ptr, spv::StorageClassFunction),
"distinct variables were structurally interned");
@@ -13020,6 +13257,7 @@ int main() {
TestNativeShaderResourceDependencies();
TestNormalizedImageContracts();
TestSpirvRequirementsAnalysis();
TestTypedSpirvSerialization();
TestDeferredSpirvPhiPatching();
TestCompilerStageInputOwnership();
TestSpirvEmissionOwnsRequirements();
@@ -13045,6 +13283,7 @@ int main() {
TestImageAddressOperands();
TestSopkCompareImmediateExtension();
TestNewShaderRecompilerCapturedVopcSdwaCmpxClass();
TestNewShaderRecompilerCapturedVopcSdwaCmpxLtU16();
TestNewShaderRecompilerIrLookupMissFailsExplicitly();
TestNewShaderRecompilerRejectsDppOn64BitCompares();
TestPsInputCountRegisterDecode();