This commit is contained in:
Rémi Bernon 2023-08-01 16:59:49 +03:00 committed by GitHub
commit abbdff4775
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 909 additions and 433 deletions

View File

@ -895,7 +895,7 @@ namespace dxvk {
info.pushConstOffset = m_pushConstOffset;
info.pushConstSize = m_pushConstSize;
return new DxvkShader(info, m_module.compile());
return new DxvkShader(info, m_module);
}

View File

@ -284,7 +284,7 @@ namespace dxvk {
info.bindings = &m_bufferBinding;
info.inputMask = m_inputMask;
return new DxvkShader(info, m_module.compile());
return new DxvkShader(info, m_module);
}
private:

View File

@ -272,7 +272,7 @@ namespace dxvk {
info.xfbStrides[i] = m_moduleInfo.xfb->strides[i];
}
return new DxvkShader(info, m_module.compile());
return new DxvkShader(info, m_module);
}

View File

@ -233,7 +233,7 @@ namespace dxvk {
if (m_programInfo.type() == DxsoProgramTypes::PixelShader)
info.flatShadingInputs = m_ps.flatShadingMask;
return new DxvkShader(info, m_module.compile());
return new DxvkShader(info, m_module);
}
void DxsoCompiler::emitInit() {

View File

@ -42,6 +42,44 @@ namespace dxvk {
return hash;
}
void DxvkShader::gatherBindingOffsets(
SpirvCodeBuffer& code,
std::vector<BindingOffsets>& offsets) {
// Run an analysis pass over the SPIR-V code to gather some
// info that we may need during pipeline compilation.
std::vector<BindingOffsets> bindingOffsets;
std::vector<uint32_t> varIds;
for (const auto& ins : code) {
if (ins.opCode() == spv::OpDecorate) {
if (ins.arg(2) == spv::DecorationBinding) {
uint32_t varId = ins.arg(1);
bindingOffsets.resize(std::max(bindingOffsets.size(), size_t(varId + 1)));
bindingOffsets[varId].bindingId = ins.arg(3);
bindingOffsets[varId].bindingOffset = ins.offset() + 3;
varIds.push_back(varId);
}
if (ins.arg(2) == spv::DecorationDescriptorSet) {
uint32_t varId = ins.arg(1);
bindingOffsets.resize(std::max(bindingOffsets.size(), size_t(varId + 1)));
bindingOffsets[varId].setOffset = ins.offset() + 3;
}
}
// Ignore the actual shader code, there's nothing interesting for us in there.
if (ins.opCode() == spv::OpFunction)
break;
}
// Combine spec constant IDs with other binding info
for (auto varId : varIds) {
BindingOffsets info = bindingOffsets[varId];
if (info.bindingOffset)
offsets.push_back(info);
}
}
DxvkShader::DxvkShader(
const DxvkShaderCreateInfo& info,
@ -75,45 +113,19 @@ namespace dxvk {
// Run an analysis pass over the SPIR-V code to gather some
// info that we may need during pipeline compilation.
std::vector<BindingOffsets> bindingOffsets;
std::vector<uint32_t> varIds;
SpirvCodeBuffer code = std::move(spirv);
uint32_t o1VarId = 0;
for (auto ins : code) {
if (ins.opCode() == spv::OpDecorate) {
if (ins.arg(2) == spv::DecorationBinding) {
uint32_t varId = ins.arg(1);
bindingOffsets.resize(std::max(bindingOffsets.size(), size_t(varId + 1)));
bindingOffsets[varId].bindingId = ins.arg(3);
bindingOffsets[varId].bindingOffset = ins.offset() + 3;
varIds.push_back(varId);
}
if (ins.arg(2) == spv::DecorationBuiltIn) {
if (ins.arg(3) == spv::BuiltInPosition)
m_flags.set(DxvkShaderFlag::ExportsPosition);
}
if (ins.arg(2) == spv::DecorationDescriptorSet) {
uint32_t varId = ins.arg(1);
bindingOffsets.resize(std::max(bindingOffsets.size(), size_t(varId + 1)));
bindingOffsets[varId].setOffset = ins.offset() + 3;
}
if (ins.arg(2) == spv::DecorationSpecId) {
if (ins.arg(3) <= MaxNumSpecConstants)
m_specConstantMask |= 1u << ins.arg(3);
}
if (ins.arg(2) == spv::DecorationLocation && ins.arg(3) == 1) {
m_o1LocOffset = ins.offset() + 3;
o1VarId = ins.arg(1);
}
if (ins.arg(2) == spv::DecorationIndex && ins.arg(1) == o1VarId)
m_o1IdxOffset = ins.offset() + 3;
}
if (ins.opCode() == spv::OpMemberDecorate) {
@ -151,12 +163,43 @@ namespace dxvk {
break;
}
// Combine spec constant IDs with other binding info
for (auto varId : varIds) {
BindingOffsets info = bindingOffsets[varId];
// Don't set pipeline library flag if the shader
// doesn't actually support pipeline libraries
m_needsLibraryCompile = canUsePipelineLibrary(true);
}
if (info.bindingOffset)
m_bindingOffsets.push_back(info);
DxvkShader::DxvkShader(
const DxvkShaderCreateInfo& info,
const SpirvModule& spirv)
: m_info (info),
m_code (spirv.compile()),
m_flags (spirv.getShaderFlags()),
m_specConstantMask (spirv.getSpecConstantMask()),
m_bindings (info.stage) {
m_info.uniformData = nullptr;
m_info.bindings = nullptr;
// Copy resource binding slot infos
for (uint32_t i = 0; i < info.bindingCount; i++) {
DxvkBindingInfo binding = info.bindings[i];
binding.stage = info.stage;
m_bindings.addBinding(binding);
}
if (info.pushConstSize) {
VkPushConstantRange pushConst;
pushConst.stageFlags = info.stage;
pushConst.offset = info.pushConstOffset;
pushConst.size = info.pushConstSize;
m_bindings.addPushConstantRange(pushConst);
}
// Copy uniform buffer data
if (info.uniformSize) {
m_uniformData.resize(info.uniformSize);
std::memcpy(m_uniformData.data(), info.uniformData, info.uniformSize);
m_info.uniformData = m_uniformData.data();
}
// Don't set pipeline library flag if the shader
@ -164,7 +207,6 @@ namespace dxvk {
m_needsLibraryCompile = canUsePipelineLibrary(true);
}
DxvkShader::~DxvkShader() {
}
@ -175,9 +217,12 @@ namespace dxvk {
const DxvkShaderModuleCreateInfo& state) const {
SpirvCodeBuffer spirvCode = m_code.decompress();
uint32_t* code = spirvCode.data();
std::vector<BindingOffsets> bindingOffsets;
gatherBindingOffsets(spirvCode, bindingOffsets);
// Remap resource binding IDs
for (const auto& info : m_bindingOffsets) {
for (const auto& info : bindingOffsets) {
auto mappedBinding = layout->lookupBinding(m_info.stage, info.bindingId);
if (mappedBinding) {
@ -190,8 +235,30 @@ namespace dxvk {
// For dual-source blending we need to re-map
// location 1, index 0 to location 0, index 1
if (state.fsDualSrcBlend && m_o1IdxOffset && m_o1LocOffset)
std::swap(code[m_o1IdxOffset], code[m_o1LocOffset]);
if (state.fsDualSrcBlend) {
uint32_t o1VarId = 0;
uint32_t o1IdxOffset = 0;
uint32_t o1LocOffset = 0;
for (auto ins : spirvCode) {
if (ins.opCode() == spv::OpDecorate) {
if (ins.arg(2) == spv::DecorationLocation && ins.arg(3) == 1) {
o1LocOffset = ins.offset() + 3;
o1VarId = ins.arg(1);
}
if (ins.arg(2) == spv::DecorationIndex && ins.arg(1) == o1VarId)
o1IdxOffset = ins.offset() + 3;
}
// Ignore the actual shader code, there's nothing interesting for us in there.
if (ins.opCode() == spv::OpFunction)
break;
}
if (o1IdxOffset && o1LocOffset)
std::swap(code[o1IdxOffset], code[o1LocOffset]);
}
// Replace undefined input variables with zero
for (uint32_t u : bit::BitMask(state.undefinedInputs))
@ -430,8 +497,9 @@ namespace dxvk {
if (numWords) {
code.beginInsertion(ins.offset());
code.erase(numWords);
code.endInsertion();
iter = SpirvInstructionIterator(code.data(), code.endInsertion(), code.dwords());
iter = SpirvInstructionIterator(code.data(), ins.offset(), code.dwords());
}
}

View File

@ -18,24 +18,6 @@ namespace dxvk {
class DxvkPipelineManager;
struct DxvkPipelineStats;
/**
* \brief Shader flags
*
* Provides extra information about the features
* used by a shader.
*/
enum DxvkShaderFlag : uint64_t {
HasSampleRateShading,
HasTransformFeedback,
ExportsPosition,
ExportsStencilRef,
ExportsViewportIndexLayerFromVertexStage,
UsesFragmentCoverage,
UsesSparseResidency,
};
using DxvkShaderFlags = Flags<DxvkShaderFlag>;
/**
* \brief Shader info
*/
@ -94,11 +76,21 @@ namespace dxvk {
class DxvkShader : public RcObject {
public:
struct BindingOffsets {
uint32_t bindingId;
uint32_t bindingOffset;
uint32_t setOffset;
};
DxvkShader(
const DxvkShaderCreateInfo& info,
SpirvCodeBuffer&& spirv);
DxvkShader(
const DxvkShaderCreateInfo& info,
const SpirvModule& spirv);
~DxvkShader();
/**
@ -246,12 +238,6 @@ namespace dxvk {
private:
struct BindingOffsets {
uint32_t bindingId;
uint32_t bindingOffset;
uint32_t setOffset;
};
DxvkShaderCreateInfo m_info;
SpirvCompressedBuffer m_code;
@ -259,14 +245,10 @@ namespace dxvk {
DxvkShaderKey m_key;
size_t m_hash = 0;
size_t m_o1IdxOffset = 0;
size_t m_o1LocOffset = 0;
uint32_t m_specConstantMask = 0;
std::atomic<bool> m_needsLibraryCompile = { true };
std::vector<char> m_uniformData;
std::vector<BindingOffsets> m_bindingOffsets;
DxvkBindingLayout m_bindings;
@ -283,6 +265,10 @@ namespace dxvk {
SpirvCodeBuffer& code,
uint32_t inputMask);
static void gatherBindingOffsets(
SpirvCodeBuffer& code,
std::vector<BindingOffsets>& offsets);
};

View File

@ -9,14 +9,12 @@ namespace dxvk {
SpirvCodeBuffer::~SpirvCodeBuffer() { }
SpirvCodeBuffer::SpirvCodeBuffer(uint32_t size)
: m_ptr(size) {
SpirvCodeBuffer::SpirvCodeBuffer(uint32_t size) {
m_code.resize(size);
}
SpirvCodeBuffer::SpirvCodeBuffer(uint32_t size, const uint32_t* data)
: m_ptr(size) {
SpirvCodeBuffer::SpirvCodeBuffer(uint32_t size, const uint32_t* data) {
m_code.resize(size);
std::memcpy(m_code.data(), data, size * sizeof(uint32_t));
}
@ -35,18 +33,20 @@ namespace dxvk {
m_code.resize(buffer.size() / sizeof(uint32_t));
std::memcpy(reinterpret_cast<char*>(m_code.data()),
buffer.data(), m_code.size() * sizeof(uint32_t));
m_ptr = m_code.size();
}
uint32_t SpirvCodeBuffer::allocId() {
constexpr size_t BoundIdsOffset = 3;
if (m_code.size() <= BoundIdsOffset)
if (this->dwords() <= BoundIdsOffset)
return 0;
return m_code[BoundIdsOffset]++;
// If we are inserting, the buffers are swapped
if (m_ptr == not_inserting)
return m_code[BoundIdsOffset]++;
else
return m_insert[BoundIdsOffset]++;
}
@ -59,92 +59,21 @@ namespace dxvk {
const uint32_t* src = other.m_code.data();
std::memcpy(dst + size, src, other.size());
m_ptr += other.m_code.size();
}
}
void SpirvCodeBuffer::putWord(uint32_t word) {
m_code.insert(m_code.begin() + m_ptr, word);
m_ptr += 1;
}
void SpirvCodeBuffer::putIns(spv::Op opCode, uint16_t wordCount) {
this->putWord(
(static_cast<uint32_t>(opCode) << 0)
| (static_cast<uint32_t>(wordCount) << 16));
}
void SpirvCodeBuffer::putInt32(uint32_t word) {
this->putWord(word);
}
void SpirvCodeBuffer::putInt64(uint64_t value) {
this->putWord(value >> 0);
this->putWord(value >> 32);
}
void SpirvCodeBuffer::putFloat32(float value) {
uint32_t tmp;
static_assert(sizeof(tmp) == sizeof(value));
std::memcpy(&tmp, &value, sizeof(value));
this->putInt32(tmp);
}
void SpirvCodeBuffer::putFloat64(double value) {
uint64_t tmp;
static_assert(sizeof(tmp) == sizeof(value));
std::memcpy(&tmp, &value, sizeof(value));
this->putInt64(tmp);
}
void SpirvCodeBuffer::putStr(const char* str) {
uint32_t word = 0;
uint32_t nbit = 0;
for (uint32_t i = 0; str[i] != '\0'; str++) {
word |= (static_cast<uint32_t>(str[i]) & 0xFF) << nbit;
if ((nbit += 8) == 32) {
this->putWord(word);
word = 0;
nbit = 0;
}
}
// Commit current word
this->putWord(word);
}
void SpirvCodeBuffer::putHeader(uint32_t version, uint32_t boundIds) {
this->putWord(spv::MagicNumber);
this->putWord(version);
this->putWord(0); // Generator
this->putWord(boundIds);
this->putWord(0); // Schema
}
void SpirvCodeBuffer::erase(size_t size) {
m_code.erase(
m_code.begin() + m_ptr,
m_code.begin() + m_ptr + size);
if (m_ptr == not_inserting)
return;
// If we are inserting, the buffers are swapped
m_insert.erase(
m_insert.begin() + m_ptr,
m_insert.begin() + m_ptr + size);
}
uint32_t SpirvCodeBuffer::strLen(const char* str) {
// Null-termination plus padding
return (std::strlen(str) + 4) / 4;
}
void SpirvCodeBuffer::store(std::ostream& stream) const {
stream.write(
reinterpret_cast<const char*>(m_code.data()),

View File

@ -5,6 +5,7 @@
#include <vector>
#include "spirv_instruction.h"
#include "spirv_writer.h"
namespace dxvk {
@ -15,8 +16,9 @@ namespace dxvk {
* Stores arbitrary SPIR-V instructions in a
* format that can be read by Vulkan drivers.
*/
class SpirvCodeBuffer {
class SpirvCodeBuffer : public SpirvWriter<SpirvCodeBuffer> {
static constexpr size_t not_inserting = ~(size_t)0;
public:
SpirvCodeBuffer();
@ -47,7 +49,7 @@ namespace dxvk {
* \returns Code size, in dwords
*/
uint32_t dwords() const {
return m_code.size();
return m_code.size() + m_insert.size();
}
/**
@ -55,7 +57,7 @@ namespace dxvk {
* \returns Code size, in bytes
*/
size_t size() const {
return m_code.size() * sizeof(uint32_t);
return this->dwords() * sizeof(uint32_t);
}
/**
@ -103,57 +105,9 @@ namespace dxvk {
* \brief Appends an 32-bit word to the buffer
* \param [in] word The word to append
*/
void putWord(uint32_t word);
/**
* \brief Appends an instruction word to the buffer
*
* Adds a single word containing both the word count
* and the op code number for a single instruction.
* \param [in] opCode Operand code
* \param [in] wordCount Number of words
*/
void putIns(spv::Op opCode, uint16_t wordCount);
/**
* \brief Appends a 32-bit integer to the buffer
* \param [in] value The number to add
*/
void putInt32(uint32_t word);
/**
* \brief Appends a 64-bit integer to the buffer
*
* A 64-bit integer will take up two 32-bit words.
* \param [in] value 64-bit value to add
*/
void putInt64(uint64_t value);
/**
* \brief Appends a 32-bit float to the buffer
* \param [in] value The number to add
*/
void putFloat32(float value);
/**
* \brief Appends a 64-bit float to the buffer
* \param [in] value The number to add
*/
void putFloat64(double value);
/**
* \brief Appends a literal string to the buffer
* \param [in] str String to append to the buffer
*/
void putStr(const char* str);
/**
* \brief Adds the header to the buffer
*
* \param [in] version SPIR-V version
* \param [in] boundIds Number of bound IDs
*/
void putHeader(uint32_t version, uint32_t boundIds);
void putWord(uint32_t word) {
m_code.push_back(word);
}
/**
* \brief Erases given number of dwords
@ -164,14 +118,6 @@ namespace dxvk {
*/
void erase(size_t size);
/**
* \brief Computes length of a literal string
*
* \param [in] str The string to check
* \returns Number of words consumed by a string
*/
uint32_t strLen(const char* str);
/**
* \brief Stores the SPIR-V module to a stream
*
@ -192,7 +138,7 @@ namespace dxvk {
* \returns Current instruction pointr
*/
size_t getInsertionPtr() const {
return m_ptr;
return m_code.size();
}
/**
@ -203,6 +149,7 @@ namespace dxvk {
* \returns Current instruction pointr
*/
void beginInsertion(size_t ptr) {
std::swap(m_code, m_insert);
m_ptr = ptr;
}
@ -214,14 +161,20 @@ namespace dxvk {
* this will restore default behaviour.
* \returns Previous instruction pointer
*/
size_t endInsertion() {
return std::exchange(m_ptr, m_code.size());
void endInsertion() {
std::swap(m_code, m_insert);
m_code.insert(m_code.begin() + m_ptr,
m_insert.begin(),
m_insert.end());
m_insert.clear();
m_ptr = not_inserting;
}
private:
std::vector<uint32_t> m_code;
size_t m_ptr = 0;
std::vector<uint32_t> m_insert;
size_t m_ptr = not_inserting;
};

View File

@ -3,81 +3,28 @@
namespace dxvk {
SpirvCompressedBuffer::SpirvCompressedBuffer()
: m_size(0) {
: m_dwords(0) {
}
SpirvCompressedBuffer::SpirvCompressedBuffer(SpirvCodeBuffer& code)
: m_size(code.dwords()) {
// The compression (detailed below) achieves roughly 55% of the
// original size on average and is very consistent, so an initial
// estimate of roughly 58% will be accurate most of the time.
const uint32_t* data = code.data();
m_code.reserve((m_size * 75) / 128);
SpirvCompressedBuffer::SpirvCompressedBuffer(
const SpirvCodeBuffer& code)
: m_dwords(code.dwords()) {
size_t dwords = m_dwords;
if (dwords == 0)
return;
std::array<uint32_t, 16> block;
uint32_t blockMask = 0;
uint32_t blockOffset = 0;
const uint32_t *src = code.data();
const uint32_t *end = src + dwords;
do {
uint32_t word = *src++;
m_code.push_back(word & 0x7f);
while (word >>= 7)
m_code.push_back((word & 0x7f) | 0x80);
} while (src != end);
// The algorithm used is a simple variable-to-fixed compression that
// encodes up to two consecutive SPIR-V tokens into one DWORD using
// a small number of different encodings. While not achieving great
// compression ratios, the main goal is to allow decompression code
// to be fast, with short dependency chains.
// Compressed tokens are stored in blocks of 16 DWORDs, each preceeded
// by a single DWORD which stores the layout for each DWORD, two bits
// each. The supported layouts, are as follows:
// 0x0: 1x 32-bit; 0x1: 1x 20-bit + 1x 12-bit
// 0x2: 2x 16-bit; 0x3: 1x 12-bit + 1x 20-bit
// These layouts are chosen to allow reasonably efficient encoding of
// opcode tokens, which usually fit into 20 bits, followed by type IDs,
// which tend to be low as well since most types are defined early.
for (size_t i = 0; i < m_size; ) {
if (likely(i + 1 < m_size)) {
uint32_t a = data[i];
uint32_t b = data[i + 1];
uint32_t schema;
uint32_t encode;
if (std::max(a, b) < (1u << 16)) {
schema = 0x2;
encode = a | (b << 16);
} else if (a < (1u << 20) && b < (1u << 12)) {
schema = 0x1;
encode = a | (b << 20);
} else if (a < (1u << 12) && b < (1u << 20)) {
schema = 0x3;
encode = a | (b << 12);
} else {
schema = 0x0;
encode = a;
}
block[blockOffset] = encode;
blockMask |= schema << (blockOffset << 1);
blockOffset += 1;
i += schema ? 2 : 1;
} else {
block[blockOffset] = data[i++];
blockOffset += 1;
}
if (unlikely(blockOffset == 16) || unlikely(i == m_size)) {
m_code.insert(m_code.end(), blockMask);
m_code.insert(m_code.end(), block.begin(), block.begin() + blockOffset);
blockMask = 0;
blockOffset = 0;
}
}
// Only shrink the array if we have lots of overhead for some reason.
// This should only happen on shaders where our initial estimate was
// too small. In general, we want to avoid reallocation here.
if (m_code.capacity() > (m_code.size() * 10) / 9)
m_code.shrink_to_fit();
m_code.shrink_to_fit();
}
@ -87,37 +34,26 @@ namespace dxvk {
SpirvCodeBuffer SpirvCompressedBuffer::decompress() const {
SpirvCodeBuffer code(m_size);
uint32_t* data = code.data();
SpirvCodeBuffer code(m_dwords);
if (m_dwords == 0)
return code;
uint32_t srcOffset = 0;
uint32_t dstOffset = 0;
constexpr uint32_t shiftAmounts = 0x0c101420;
while (dstOffset < m_size) {
uint32_t blockMask = m_code[srcOffset];
for (uint32_t i = 0; i < 16 && dstOffset < m_size; i++) {
// Use 64-bit integers for some of the operands so we can
// shift by 32 bits and not handle it as a special cases
uint32_t schema = (blockMask >> (i << 1)) & 0x3;
uint32_t shift = (shiftAmounts >> (schema << 3)) & 0xff;
uint64_t mask = ~(~0ull << shift);
uint64_t encode = m_code[srcOffset + i + 1];
data[dstOffset] = encode & mask;
if (likely(schema))
data[dstOffset + 1] = encode >> shift;
dstOffset += schema ? 2 : 1;
}
srcOffset += 17;
}
uint32_t *dst = code.data();
const uint8_t *src = m_code.data();
const uint8_t *end = src + m_code.size();
do {
*dst = *src++;
if (src == end || !(*src & 0x80)) continue;
*dst |= (*src++ & 0x7f) << 7;
if (src == end || !(*src & 0x80)) continue;
*dst |= (*src++ & 0x7f) << 14;
if (src == end || !(*src & 0x80)) continue;
*dst |= (*src++ & 0x7f) << 21;
if (src == end || !(*src & 0x80)) continue;
*dst |= (*src++ & 0x7f) << 28;
} while (++dst, src != end);
return code;
}
}
}

View File

@ -4,6 +4,8 @@
#include "spirv_code_buffer.h"
#include "../util/util_raw_vector.h"
namespace dxvk {
/**
@ -12,27 +14,115 @@ namespace dxvk {
* Implements a fast in-memory compression
* to keep memory footprint low.
*/
class SpirvCompressedBuffer {
class SpirvCompressedBuffer : public SpirvWriter<SpirvCompressedBuffer> {
static constexpr size_t not_inserting = ~(size_t)0;
public:
SpirvCompressedBuffer();
SpirvCompressedBuffer(SpirvCodeBuffer& code);
SpirvCompressedBuffer(const SpirvCodeBuffer& code);
SpirvCompressedBuffer(SpirvCompressedBuffer&& other) = default;
~SpirvCompressedBuffer();
void shrink() { m_code.shrink_to_fit(); }
/**
* \brief Code size, in dwords
* \returns Code size, in dwords
*/
uint32_t dwords() const { return m_dwords; }
/**
* \brief Code size, in bytes
* \returns Code size, in bytes
*/
size_t size() const { return m_code.size() + m_insert.size(); }
/**
* \brief Appends an 32-bit word to the buffer
* \param [in] word The word to append
*/
void putWord(uint32_t word) {
size_t size = m_code.size();
m_code.resize(size + 5);
uint8_t *dst = m_code.data() + size;
*dst++ = word & 0x7f;
while (word >>= 7) *dst++ = (word & 0x7f) | 0x80;
m_code.resize(dst - m_code.data());
m_dwords += 1;
}
/**
* \brief Merges two code buffers
*
* This is useful to generate declarations or
* the SPIR-V header at the same time as the
* code when doing so in advance is impossible.
* \param [in] other Code buffer to append
*/
void append(const SpirvCompressedBuffer& other) {
m_code.insert(m_code.end(),
other.m_code.begin(),
other.m_code.end());
m_dwords += other.dwords();
}
/**
* \brief Retrieves current insertion pointer
*
* Sometimes it may be necessay to insert code into the
* middle of the stream rather than appending it. This
* retrieves the current function pointer. Note that the
* pointer will become invalid if any code is inserted
* before the current pointer location.
* \returns Current instruction pointr
*/
size_t getInsertionPtr() const {
return m_code.size();
}
/**
* \brief Sets insertion pointer to a specific value
*
* Sets the insertion pointer to a value that was
* previously retrieved by \ref getInsertionPtr.
* \returns Current instruction pointr
*/
void beginInsertion(size_t ptr) {
std::swap(m_code, m_insert);
m_ptr = ptr;
}
/**
* \brief Sets insertion pointer to the end
*
* After this call, new instructions will be
* appended to the stream. In other words,
* this will restore default behaviour.
*/
void endInsertion() {
std::swap(m_code, m_insert);
m_code.insert(m_code.begin() + m_ptr,
m_insert.begin(),
m_insert.end());
m_insert.clear();
m_ptr = not_inserting;
}
SpirvCodeBuffer decompress() const;
private:
size_t m_size;
std::vector<uint32_t> m_code;
void encodeDword(uint32_t dw);
uint32_t decodeDword(size_t& offset) const;
uint32_t m_dwords;
raw_vector<uint8_t> m_code;
raw_vector<uint8_t> m_insert;
size_t m_ptr = not_inserting;
};
}
}

View File

@ -15,8 +15,8 @@ namespace dxvk {
}
SpirvCodeBuffer SpirvModule::compile() const {
SpirvCodeBuffer result;
SpirvCompressedBuffer SpirvModule::compile() const {
SpirvCompressedBuffer result;
result.putHeader(m_version, m_id);
result.append(m_capabilities);
result.append(m_extensions);
@ -29,6 +29,7 @@ namespace dxvk {
result.append(m_typeConstDefs);
result.append(m_variables);
result.append(m_code);
result.shrink();
return result;
}
@ -40,12 +41,7 @@ namespace dxvk {
bool SpirvModule::hasCapability(
spv::Capability capability) {
for (auto ins : m_capabilities) {
if (ins.opCode() == spv::OpCapability && ins.arg(1) == capability)
return true;
}
return false;
return m_enabledCaps.find(capability) != m_enabledCaps.end();
}
void SpirvModule::enableCapability(
@ -55,6 +51,20 @@ namespace dxvk {
if (!hasCapability(capability)) {
m_capabilities.putIns (spv::OpCapability, 2);
m_capabilities.putWord(capability);
m_enabledCaps.insert(capability);
if (capability == spv::CapabilitySampleRateShading)
m_shaderFlags.set(DxvkShaderFlag::HasSampleRateShading);
if (capability == spv::CapabilityShaderViewportIndex
|| capability == spv::CapabilityShaderLayer)
m_shaderFlags.set(DxvkShaderFlag::ExportsViewportIndexLayerFromVertexStage);
if (capability == spv::CapabilitySparseResidency)
m_shaderFlags.set(DxvkShaderFlag::UsesSparseResidency);
if (capability == spv::CapabilityFragmentFullyCoveredEXT)
m_shaderFlags.set(DxvkShaderFlag::UsesFragmentCoverage);
}
}
@ -95,6 +105,13 @@ namespace dxvk {
m_execModeInfo.putIns (spv::OpExecutionMode, 3);
m_execModeInfo.putWord(entryPointId);
m_execModeInfo.putWord(executionMode);
m_enabledModes.insert(executionMode);
if (executionMode == spv::ExecutionModeStencilRefReplacingEXT)
m_shaderFlags.set(DxvkShaderFlag::ExportsStencilRef);
if (executionMode == spv::ExecutionModeXfb)
m_shaderFlags.set(DxvkShaderFlag::HasTransformFeedback);
}
@ -109,6 +126,12 @@ namespace dxvk {
for (uint32_t i = 0; i < argCount; i++)
m_execModeInfo.putWord(args[i]);
if (executionMode == spv::ExecutionModeStencilRefReplacingEXT)
m_shaderFlags.set(DxvkShaderFlag::ExportsStencilRef);
if (executionMode == spv::ExecutionModeXfb)
m_shaderFlags.set(DxvkShaderFlag::HasTransformFeedback);
}
@ -197,89 +220,123 @@ namespace dxvk {
uint32_t SpirvModule::constBool(
bool v) {
return this->defConst(v
? spv::OpConstantTrue
: spv::OpConstantFalse,
this->defBoolType(),
0, nullptr);
return this->defConstCached(m_constBool[v ? 1 : 0],
v ? spv::OpConstantTrue : spv::OpConstantFalse,
this->defBoolType(), 0, nullptr);
}
uint32_t SpirvModule::consti32(
int32_t v) {
auto it = m_constSInt32.find(v);
if (it != m_constSInt32.end()) return it->second;
std::array<uint32_t, 1> data;
std::memcpy(data.data(), &v, sizeof(v));
return this->defConst(
auto id = this->defConstUnique(
spv::OpConstant,
this->defIntType(32, 1),
data.size(),
data.data());
m_constSInt32[v] = id;
return id;
}
uint32_t SpirvModule::consti64(
int64_t v) {
auto it = m_constSInt64.find(v);
if (it != m_constSInt64.end()) return it->second;
std::array<uint32_t, 2> data;
std::memcpy(data.data(), &v, sizeof(v));
return this->defConst(
auto id = this->defConstUnique(
spv::OpConstant,
this->defIntType(64, 1),
data.size(),
data.data());
m_constSInt64[v] = id;
return id;
}
uint32_t SpirvModule::constu32(
uint32_t v) {
auto it = m_constUInt32.find(v);
if (it != m_constUInt32.end()) return it->second;
std::array<uint32_t, 1> data;
std::memcpy(data.data(), &v, sizeof(v));
return this->defConst(
auto id = this->defConstUnique(
spv::OpConstant,
this->defIntType(32, 0),
data.size(),
data.data());
m_constUInt32[v] = id;
return id;
}
uint32_t SpirvModule::constu64(
uint64_t v) {
auto it = m_constUInt64.find(v);
if (it != m_constUInt64.end()) return it->second;
std::array<uint32_t, 2> data;
std::memcpy(data.data(), &v, sizeof(v));
return this->defConst(
auto id = this->defConstUnique(
spv::OpConstant,
this->defIntType(64, 0),
data.size(),
data.data());
m_constUInt64[v] = id;
return id;
}
uint32_t SpirvModule::constf32(
float v) {
auto it = m_constFloat32.find(v);
if (it != m_constFloat32.end()) return it->second;
std::array<uint32_t, 1> data;
std::memcpy(data.data(), &v, sizeof(v));
return this->defConst(
auto id = this->defConstUnique(
spv::OpConstant,
this->defFloatType(32),
data.size(),
data.data());
m_constFloat32[v] = id;
return id;
}
uint32_t SpirvModule::constf64(
double v) {
auto it = m_constFloat64.find(v);
if (it != m_constFloat64.end()) return it->second;
std::array<uint32_t, 2> data;
std::memcpy(data.data(), &v, sizeof(v));
return this->defConst(
auto id = this->defConstUnique(
spv::OpConstant,
this->defFloatType(64),
data.size(),
data.data());
m_constFloat64[v] = id;
return id;
}
@ -465,15 +522,20 @@ namespace dxvk {
uint32_t SpirvModule::constUndef(
uint32_t typeId) {
return this->defConst(spv::OpUndef,
auto it = m_constUndef.find(typeId);
if (it != m_constUndef.end()) return it->second;
auto id = this->defConstUnique(spv::OpUndef,
typeId, 0, nullptr);
m_constUndef[typeId] = id;
return id;
}
uint32_t SpirvModule::lateConst32(
uint32_t typeId) {
uint32_t resultId = this->allocateId();
m_lateConsts.insert(resultId);
m_lateConsts[resultId] = m_typeConstDefs.getInsertionPtr();
m_typeConstDefs.putIns (spv::OpConstant, 4);
m_typeConstDefs.putWord(typeId);
@ -486,19 +548,12 @@ namespace dxvk {
void SpirvModule::setLateConst(
uint32_t constId,
const uint32_t* argIds) {
for (auto ins : m_typeConstDefs) {
if (ins.opCode() != spv::OpConstant
&& ins.opCode() != spv::OpConstantComposite)
continue;
if (ins.arg(2) != constId)
continue;
auto ins = SpirvInstruction(m_typeConstDefs.data(),
m_lateConsts[constId],
m_typeConstDefs.dwords());
for (uint32_t i = 3; i < ins.length(); i++)
ins.setArg(i, argIds[i - 3]);
return;
}
for (uint32_t i = 3; i < ins.length(); i++)
ins.setArg(i, argIds[i - 3]);
}
@ -574,6 +629,9 @@ namespace dxvk {
m_annotations.putWord (object);
m_annotations.putWord (spv::DecorationBuiltIn);
m_annotations.putWord (builtIn);
if (builtIn == spv::BuiltInPosition)
m_shaderFlags.set(DxvkShaderFlag::ExportsPosition);
}
@ -624,6 +682,8 @@ namespace dxvk {
m_annotations.putWord (object);
m_annotations.putWord (spv::DecorationSpecId);
m_annotations.putInt32(specId);
if (specId <= MaxNumSpecConstants)
m_specConstantMask |= 1u << specId;
}
@ -664,6 +724,9 @@ namespace dxvk {
m_annotations.putWord (memberId);
m_annotations.putWord (spv::DecorationBuiltIn);
m_annotations.putWord (builtIn);
if (builtIn == spv::BuiltInPosition)
m_shaderFlags.set(DxvkShaderFlag::ExportsPosition);
}
@ -703,12 +766,14 @@ namespace dxvk {
uint32_t SpirvModule::defVoidType() {
return this->defType(spv::OpTypeVoid, 0, nullptr);
return this->defTypeCached(m_typeVoid,
spv::OpTypeVoid, 0, nullptr);
}
uint32_t SpirvModule::defBoolType() {
return this->defType(spv::OpTypeBool, 0, nullptr);
return this->defTypeCached(m_typeBool[0],
spv::OpTypeBool, 0, nullptr);
}
@ -716,6 +781,32 @@ namespace dxvk {
uint32_t width,
uint32_t isSigned) {
std::array<uint32_t, 2> args = {{ width, isSigned }};
switch ((int64_t)width * (isSigned ? -1 : 1)) {
case -64:
return this->defTypeCached(m_typeSInt64[0],
spv::OpTypeInt, args.size(), args.data());
case -32:
return this->defTypeCached(m_typeSInt32[0],
spv::OpTypeInt, args.size(), args.data());
case -16:
return this->defTypeCached(m_typeSInt16[0],
spv::OpTypeInt, args.size(), args.data());
case -8:
return this->defTypeCached(m_typeSInt8[0],
spv::OpTypeInt, args.size(), args.data());
case +8:
return this->defTypeCached(m_typeUInt8[0],
spv::OpTypeInt, args.size(), args.data());
case +16:
return this->defTypeCached(m_typeUInt16[0],
spv::OpTypeInt, args.size(), args.data());
case +32:
return this->defTypeCached(m_typeUInt32[0],
spv::OpTypeInt, args.size(), args.data());
case +64:
return this->defTypeCached(m_typeUInt64[0],
spv::OpTypeInt, args.size(), args.data());
}
return this->defType(spv::OpTypeInt,
args.size(), args.data());
}
@ -724,6 +815,17 @@ namespace dxvk {
uint32_t SpirvModule::defFloatType(
uint32_t width) {
std::array<uint32_t, 1> args = {{ width }};
switch (width) {
case 16:
return this->defTypeCached(m_typeFloat16[0],
spv::OpTypeFloat, args.size(), args.data());
case 32:
return this->defTypeCached(m_typeFloat32[0],
spv::OpTypeFloat, args.size(), args.data());
case 64:
return this->defTypeCached(m_typeFloat64[0],
spv::OpTypeFloat, args.size(), args.data());
}
return this->defType(spv::OpTypeFloat,
args.size(), args.data());
}
@ -734,6 +836,43 @@ namespace dxvk {
uint32_t elementCount) {
std::array<uint32_t, 2> args =
{{ elementType, elementCount }};
if (elementType == m_typeBool[0])
return this->defTypeCached(m_typeBool[elementCount - 1],
spv::OpTypeVector, args.size(), args.data());
else if (elementType == m_typeSInt8[0])
return this->defTypeCached(m_typeSInt8[elementCount - 1],
spv::OpTypeVector, args.size(), args.data());
else if (elementType == m_typeSInt16[0])
return this->defTypeCached(m_typeSInt16[elementCount - 1],
spv::OpTypeVector, args.size(), args.data());
else if (elementType == m_typeSInt32[0])
return this->defTypeCached(m_typeSInt32[elementCount - 1],
spv::OpTypeVector, args.size(), args.data());
else if (elementType == m_typeSInt64[0])
return this->defTypeCached(m_typeSInt64[elementCount - 1],
spv::OpTypeVector, args.size(), args.data());
else if (elementType == m_typeUInt8[0])
return this->defTypeCached(m_typeUInt8[elementCount - 1],
spv::OpTypeVector, args.size(), args.data());
else if (elementType == m_typeUInt16[0])
return this->defTypeCached(m_typeUInt16[elementCount - 1],
spv::OpTypeVector, args.size(), args.data());
else if (elementType == m_typeUInt32[0])
return this->defTypeCached(m_typeUInt32[elementCount - 1],
spv::OpTypeVector, args.size(), args.data());
else if (elementType == m_typeUInt64[0])
return this->defTypeCached(m_typeUInt64[elementCount - 1],
spv::OpTypeVector, args.size(), args.data());
else if (elementType == m_typeFloat16[0])
return this->defTypeCached(m_typeFloat16[elementCount - 1],
spv::OpTypeVector, args.size(), args.data());
else if (elementType == m_typeFloat32[0])
return this->defTypeCached(m_typeFloat32[elementCount - 1],
spv::OpTypeVector, args.size(), args.data());
else if (elementType == m_typeFloat64[0])
return this->defTypeCached(m_typeFloat64[elementCount - 1],
spv::OpTypeVector, args.size(), args.data());
return this->defType(spv::OpTypeVector,
args.size(), args.data());
@ -764,13 +903,11 @@ namespace dxvk {
uint32_t SpirvModule::defArrayTypeUnique(
uint32_t typeId,
uint32_t length) {
uint32_t resultId = this->allocateId();
std::array<uint32_t, 2> args = {{ typeId, length }};
m_typeConstDefs.putIns (spv::OpTypeArray, 4);
m_typeConstDefs.putWord(resultId);
m_typeConstDefs.putWord(typeId);
m_typeConstDefs.putWord(length);
return resultId;
m_typeLocs.push_back(m_typeConstDefs.getInsertionPtr());
return this->defTypeUnique(spv::OpTypeArray,
args.size(), args.data());
}
@ -785,12 +922,11 @@ namespace dxvk {
uint32_t SpirvModule::defRuntimeArrayTypeUnique(
uint32_t typeId) {
uint32_t resultId = this->allocateId();
std::array<uint32_t, 1> args = { typeId };
m_typeConstDefs.putIns (spv::OpTypeRuntimeArray, 3);
m_typeConstDefs.putWord(resultId);
m_typeConstDefs.putWord(typeId);
return resultId;
m_typeLocs.push_back(m_typeConstDefs.getInsertionPtr());
return this->defTypeUnique(spv::OpTypeRuntimeArray,
args.size(), args.data());
}
@ -820,14 +956,9 @@ namespace dxvk {
uint32_t SpirvModule::defStructTypeUnique(
uint32_t memberCount,
const uint32_t* memberTypes) {
uint32_t resultId = this->allocateId();
m_typeConstDefs.putIns (spv::OpTypeStruct, 2 + memberCount);
m_typeConstDefs.putWord(resultId);
for (uint32_t i = 0; i < memberCount; i++)
m_typeConstDefs.putWord(memberTypes[i]);
return resultId;
m_typeLocs.push_back(m_typeConstDefs.getInsertionPtr());
return this->defTypeUnique(spv::OpTypeStruct,
memberCount, memberTypes);
}
@ -845,7 +976,8 @@ namespace dxvk {
uint32_t SpirvModule::defSamplerType() {
return this->defType(spv::OpTypeSampler, 0, nullptr);
return this->defTypeCached(m_typeSampler,
spv::OpTypeSampler, 0, nullptr);
}
@ -3691,6 +3823,32 @@ namespace dxvk {
m_code.putWord(streamId);
}
}
uint32_t SpirvModule::defTypeCached(
std::optional<uint32_t>&cache,
spv::Op op,
uint32_t argCount,
const uint32_t* argIds) {
if (!cache)
cache = this->defTypeUnique(op, argCount,
argIds);
return *cache;
}
uint32_t SpirvModule::defTypeUnique(
spv::Op op,
uint32_t argCount,
const uint32_t* argIds) {
uint32_t resultId = this->allocateId();
m_typeConstDefs.putIns (op, 2 + argCount);
m_typeConstDefs.putWord(resultId);
for (uint32_t i = 0; i < argCount; i++)
m_typeConstDefs.putWord(argIds[i]);
return resultId;
}
void SpirvModule::opBeginInvocationInterlock() {
@ -3710,52 +3868,38 @@ namespace dxvk {
// Since the type info is stored in the code buffer,
// we can use the code buffer to look up type IDs as
// well. Result IDs are always stored as argument 1.
for (auto ins : m_typeConstDefs) {
bool match = ins.opCode() == op
&& ins.length() == 2 + argCount;
for (uint32_t i = 0; i < argCount && match; i++)
match &= ins.arg(2 + i) == argIds[i];
if (match)
return ins.arg(1);
const uint32_t *data = m_typeConstDefs.data();
for (auto i : m_typeLocs) {
if ((data[i] & spv::OpCodeMask) == op &&
(data[i] >> spv::WordCountShift) == (2 + argCount) &&
(!argCount || !std::memcmp(data + i + 2, argIds, argCount * sizeof(uint32_t))))
return data[i + 1];
}
// Type not yet declared, create a new one.
uint32_t resultId = this->allocateId();
m_typeConstDefs.putIns (op, 2 + argCount);
m_typeConstDefs.putWord(resultId);
for (uint32_t i = 0; i < argCount; i++)
m_typeConstDefs.putWord(argIds[i]);
return resultId;
m_typeLocs.push_back(m_typeConstDefs.getInsertionPtr());
return this->defTypeUnique(op, argCount, argIds);
}
uint32_t SpirvModule::defConst(
uint32_t SpirvModule::defConstCached(
std::optional<uint32_t>&cache,
spv::Op op,
uint32_t typeId,
uint32_t argCount,
const uint32_t* argIds) {
// Avoid declaring constants multiple times
for (auto ins : m_typeConstDefs) {
bool match = ins.opCode() == op
&& ins.length() == 3 + argCount
&& ins.arg(1) == typeId;
for (uint32_t i = 0; i < argCount && match; i++)
match &= ins.arg(3 + i) == argIds[i];
if (!match)
continue;
uint32_t id = ins.arg(2);
if (!cache)
cache = this->defConstUnique(op, typeId,
argCount, argIds);
return *cache;
}
if (m_lateConsts.find(id) == m_lateConsts.end())
return id;
}
// Constant not yet declared, make a new one
uint32_t SpirvModule::defConstUnique(
spv::Op op,
uint32_t typeId,
uint32_t argCount,
const uint32_t* argIds) {
uint32_t resultId = this->allocateId();
m_typeConstDefs.putIns (op, 3 + argCount);
m_typeConstDefs.putWord(typeId);
@ -3765,6 +3909,28 @@ namespace dxvk {
m_typeConstDefs.putWord(argIds[i]);
return resultId;
}
uint32_t SpirvModule::defConst(
spv::Op op,
uint32_t typeId,
uint32_t argCount,
const uint32_t* argIds) {
// Avoid declaring constants multiple times
const uint32_t *data = m_typeConstDefs.data();
for (auto i : m_constLocs) {
if ((data[i] & spv::OpCodeMask) == op &&
(data[i] >> spv::WordCountShift) == (3 + argCount) &&
data[i + 1] == typeId &&
(!argCount || !std::memcmp(data + i + 3, argIds, argCount * sizeof(uint32_t))) &&
m_lateConsts.find(data[i + 2]) == m_lateConsts.end())
return data[i + 2];
}
// Constant not yet declared, make a new one
m_constLocs.push_back(m_typeConstDefs.getInsertionPtr());
return this->defConstUnique(op, typeId, argCount, argIds);
}
void SpirvModule::instImportGlsl450() {

View File

@ -3,9 +3,30 @@
#include <unordered_set>
#include "spirv_code_buffer.h"
#include "spirv_compression.h"
#include "../dxvk/dxvk_limits.h"
namespace dxvk {
/**
* \brief Shader flags
*
* Provides extra information about the features
* used by a shader.
*/
enum DxvkShaderFlag : uint64_t {
HasSampleRateShading,
HasTransformFeedback,
ExportsPosition,
ExportsStencilRef,
ExportsViewportIndexLayerFromVertexStage,
UsesFragmentCoverage,
UsesSparseResidency,
};
using DxvkShaderFlags = Flags<DxvkShaderFlag>;
struct SpirvPhiLabel {
uint32_t varId = 0;
uint32_t labelId = 0;
@ -59,7 +80,7 @@ namespace dxvk {
~SpirvModule();
SpirvCodeBuffer compile() const;
SpirvCompressedBuffer compile() const;
size_t getInsertionPtr() {
return m_code.getInsertionPtr();
@ -1264,6 +1285,14 @@ namespace dxvk {
void opEndInvocationInterlock();
DxvkShaderFlags getShaderFlags() const {
return m_shaderFlags;
}
uint32_t getSpecConstantMask() const {
return m_specConstantMask;
}
private:
uint32_t m_version;
@ -1271,27 +1300,84 @@ namespace dxvk {
uint32_t m_instExtGlsl450 = 0;
uint32_t m_blockId = 0;
SpirvCodeBuffer m_capabilities;
SpirvCodeBuffer m_extensions;
SpirvCodeBuffer m_instExt;
SpirvCodeBuffer m_memoryModel;
SpirvCodeBuffer m_entryPoints;
SpirvCodeBuffer m_execModeInfo;
SpirvCodeBuffer m_debugNames;
SpirvCodeBuffer m_annotations;
SpirvCodeBuffer m_typeConstDefs;
SpirvCodeBuffer m_variables;
SpirvCodeBuffer m_code;
DxvkShaderFlags m_shaderFlags;
uint32_t m_specConstantMask = 0;
std::unordered_set<uint32_t> m_lateConsts;
SpirvCompressedBuffer m_capabilities;
SpirvCompressedBuffer m_extensions;
SpirvCompressedBuffer m_instExt;
SpirvCompressedBuffer m_memoryModel;
SpirvCompressedBuffer m_entryPoints;
SpirvCompressedBuffer m_execModeInfo;
SpirvCompressedBuffer m_debugNames;
SpirvCompressedBuffer m_annotations;
SpirvCodeBuffer m_typeConstDefs;
SpirvCompressedBuffer m_variables;
SpirvCompressedBuffer m_code;
std::unordered_set<spv::Capability> m_enabledCaps;
std::unordered_set<spv::ExecutionMode> m_enabledModes;
std::vector<size_t> m_typeLocs;
std::vector<size_t> m_constLocs;
std::optional<uint32_t> m_typeVoid;
std::optional<uint32_t> m_typeSampler;
std::optional<uint32_t> m_typeBool[4];
std::optional<uint32_t> m_typeSInt8[4];
std::optional<uint32_t> m_typeSInt16[4];
std::optional<uint32_t> m_typeSInt32[4];
std::optional<uint32_t> m_typeSInt64[4];
std::optional<uint32_t> m_typeUInt8[4];
std::optional<uint32_t> m_typeUInt16[4];
std::optional<uint32_t> m_typeUInt32[4];
std::optional<uint32_t> m_typeUInt64[4];
std::optional<uint32_t> m_typeFloat16[4];
std::optional<uint32_t> m_typeFloat32[4];
std::optional<uint32_t> m_typeFloat64[4];
std::optional<uint32_t> m_constBool[2];
std::unordered_map<uint32_t, uint32_t> m_constUndef;
std::unordered_map<int32_t, uint32_t> m_constSInt32;
std::unordered_map<int64_t, uint32_t> m_constSInt64;
std::unordered_map<uint32_t, uint32_t> m_constUInt32;
std::unordered_map<uint64_t, uint32_t> m_constUInt64;
std::unordered_map<float, uint32_t> m_constFloat32;
std::unordered_map<double, uint32_t> m_constFloat64;
std::unordered_map<uint32_t, size_t> m_lateConsts;
std::vector<uint32_t> m_interfaceVars;
uint32_t defTypeCached(
std::optional<uint32_t>&cache,
spv::Op op,
uint32_t argCount,
const uint32_t* argIds);
uint32_t defTypeUnique(
spv::Op op,
uint32_t argCount,
const uint32_t* argIds);
uint32_t defType(
spv::Op op,
uint32_t argCount,
const uint32_t* argIds);
uint32_t defConstCached(
std::optional<uint32_t>&cache,
spv::Op op,
uint32_t typeId,
uint32_t argCount,
const uint32_t* argIds);
uint32_t defConstUnique(
spv::Op op,
uint32_t typeId,
uint32_t argCount,
const uint32_t* argIds);
uint32_t defConst(
spv::Op op,
uint32_t typeId,
@ -1317,4 +1403,4 @@ namespace dxvk {
};
}
}

125
src/spirv/spirv_writer.h Normal file
View File

@ -0,0 +1,125 @@
#pragma once
#include "spirv_instruction.h"
namespace dxvk {
/**
* \brief SPIR-V code buffer
*
* Helper class for generating SPIR-V shaders.
* Stores arbitrary SPIR-V instructions in a
* format that can be read by Vulkan drivers.
*/
template<class SpirvBuffer>
class SpirvWriter {
public:
/**
* \brief Appends an 32-bit word to the buffer
* \param [in] word The word to append
*/
void putWord(uint32_t word) {
static_cast<SpirvBuffer*>(this)->putWord(word);
}
/**
* \brief Appends an instruction word to the buffer
*
* Adds a single word containing both the word count
* and the op code number for a single instruction.
* \param [in] opCode Operand code
* \param [in] wordCount Number of words
*/
void putIns(spv::Op opCode, uint16_t wordCount) {
this->putWord(
(static_cast<uint32_t>(opCode) << 0)
| (static_cast<uint32_t>(wordCount) << 16));
}
/**
* \brief Appends a 32-bit integer to the buffer
* \param [in] value The number to add
*/
void putInt32(uint32_t word) {
this->putWord(word);
}
/**
* \brief Appends a 64-bit integer to the buffer
*
* A 64-bit integer will take up two 32-bit words.
* \param [in] value 64-bit value to add
*/
void putInt64(uint64_t value) {
this->putWord(value >> 0);
this->putWord(value >> 32);
}
/**
* \brief Appends a 32-bit float to the buffer
* \param [in] value The number to add
*/
void putFloat32(float value) {
this->putInt32(bit::cast<uint32_t>(value));
}
/**
* \brief Appends a 64-bit float to the buffer
* \param [in] value The number to add
*/
void putFloat64(double value) {
this->putInt64(bit::cast<uint64_t>(value));
}
/**
* \brief Appends a literal string to the buffer
* \param [in] str String to append to the buffer
*/
void putStr(const char* str) {
uint32_t word = 0;
uint32_t nbit = 0;
for (uint32_t i = 0; str[i] != '\0'; str++) {
word |= (static_cast<uint32_t>(str[i]) & 0xFF) << nbit;
if ((nbit += 8) == 32) {
this->putWord(word);
word = 0;
nbit = 0;
}
}
// Commit current word
this->putWord(word);
}
/**
* \brief Adds the header to the buffer
*
* \param [in] version SPIR-V version
* \param [in] boundIds Number of bound IDs
*/
void putHeader(uint32_t version, uint32_t boundIds) {
this->putWord(spv::MagicNumber);
this->putWord(version);
this->putWord(0); // Generator
this->putWord(boundIds);
this->putWord(0); // Schema
}
/**
* \brief Computes length of a literal string
*
* \param [in] str The string to check
* \returns Number of words consumed by a string
*/
uint32_t strLen(const char* str) {
// Null-termination plus padding
return (std::strlen(str) + 4) / 4;
}
};
}

137
src/util/util_raw_vector.h Normal file
View File

@ -0,0 +1,137 @@
#pragma once
#include <type_traits>
#include <memory>
namespace dxvk {
/**
* \brief Uninitialized vector
*
* Implements a vector with uinitialized storage to avoid
* std::vector default initialization.
*/
template<typename T>
class raw_vector {
struct deleter {
void operator()(void* p) const { std::free(p); }
};
using pointer_type = std::unique_ptr<T, deleter>;
public:
void reserve(size_t n) {
n = pick_capacity(n);
if (n > m_capacity)
reallocate(n);
}
void shrink_to_fit() {
size_t n = pick_capacity(m_size);
reallocate(n);
}
void resize(size_t n) {
if (n >= m_size) {
reserve(n);
std::uninitialized_default_construct(ptr(m_size), ptr(n));
}
m_size = n;
}
void push_back(const T& object) {
reserve(m_size + 1);
*ptr(m_size++) = object;
}
void push_back(T&& object) {
reserve(m_size + 1);
*ptr(m_size++) = std::move(object);
}
template<typename... Args>
void emplace_back(Args... args) {
reserve(m_size + 1);
*ptr(m_size++) = T(std::forward<Args>(args)...);
}
void erase(size_t idx) {
if (idx < m_size)
std::memmove(ptr(idx), ptr(idx + 1), (m_size - idx) * sizeof(T));
m_size -= 1;
}
void insert(const T* pos, const T* begin, const T* end) {
if (begin == end)
return;
size_t off = pos - ptr(0);
size_t size = m_size;
size_t count = (end - begin);
resize(size + count);
if (off < size)
std::memmove(ptr(off) + count, ptr(off),
(size - off) * sizeof(T));
std::memcpy(ptr(off), begin, count * sizeof(T));
}
void pop_back() { m_size--; }
void clear() { m_size = 0; }
size_t size() const { return m_size; }
const T* data() const { return ptr(0); }
T* data() { return ptr(0); }
const T* begin() const { return ptr(0); }
T* begin() { return ptr(0); }
const T* end() const { return ptr(m_size); }
T* end() { return ptr(m_size); }
T& operator [] (size_t idx) { return *ptr(idx); }
const T& operator [] (size_t idx) const { return *ptr(idx); }
T& front() { return *ptr(0); }
const T& front() const { return *ptr(0); }
T& back() { return *ptr(m_size - 1); }
const T& back() const { return *ptr(m_size - 1); }
private:
pointer_type m_ptr = nullptr;
size_t m_size = 0;
size_t m_capacity = 0;
size_t pick_capacity(size_t n) {
size_t capacity = m_capacity;
if (capacity < 128)
capacity = 128;
while (capacity < n)
capacity *= 2;
return capacity;
}
void reallocate(size_t n) {
void* ptr = std::realloc(m_ptr.get(), n * sizeof(T));
m_ptr.release();
m_ptr.reset(static_cast<T*>(ptr));
m_capacity = n;
}
T* ptr(size_t idx) {
return reinterpret_cast<T*>(m_ptr.get()) + idx;
}
const T* ptr(size_t idx) const {
return reinterpret_cast<const T*>(m_ptr.get()) + idx;
}
};
}