From 74722fa693b9f74c2e3e3a79f69ba2ae675e60d9 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Wed, 10 Jan 2018 18:58:17 +0100 Subject: [PATCH] [dxvk] Implementing unbound resource handling (4/4) The shader compiler now queries whether a constant buffer or texture is bound before trying to access it for reading. This is not yet implemented for image fetch operations, atomic operations, or buffer load/store ops. --- src/dxbc/dxbc_compiler.cpp | 217 ++++++++++++++++++++++++++++++++++--- src/dxbc/dxbc_compiler.h | 17 +++ src/dxvk/dxvk_context.cpp | 14 --- 3 files changed, 217 insertions(+), 31 deletions(-) diff --git a/src/dxbc/dxbc_compiler.cpp b/src/dxbc/dxbc_compiler.cpp index 83a2334b4..16410da42 100644 --- a/src/dxbc/dxbc_compiler.cpp +++ b/src/dxbc/dxbc_compiler.cpp @@ -1878,6 +1878,7 @@ namespace dxvk { // bufinfo takes two arguments // (dst0) The destination register // (src0) The buffer register to query + // TODO Check if resource is bound const DxbcBufferInfo bufferInfo = getBufferInfo(ins.src[0]); // We'll store this as a scalar unsigned integer @@ -1912,6 +1913,7 @@ namespace dxvk { // (src0) Structure index // (src1) Byte offset // (src2) Source register + // TODO Check if resource is bound const bool isStructured = ins.op == DxbcOpcode::LdStructured; // Source register. The exact way we access @@ -1946,6 +1948,7 @@ namespace dxvk { // (src0) Structure index // (src1) Byte offset // (src2) Source register + // TODO Check if resource is bound const bool isStructured = ins.op == DxbcOpcode::StoreStructured; // Source register. The exact way we access @@ -2033,6 +2036,7 @@ namespace dxvk { // (dst0) The destination register // (src0) Resource LOD to query // (src1) Resource to query + // TODO Check if resource is bound const DxbcResinfoType resinfoType = ins.controls.resinfoType; if (ins.src[1].type != DxbcOperandType::Resource) { @@ -2162,6 +2166,7 @@ namespace dxvk { // (dst0) The destination register // (src0) Source address // (src1) Source texture + // TODO Check if resource is bound const uint32_t textureId = ins.src[1].idx[0].offset; // Image type, which stores the image dimensions etc. @@ -2209,12 +2214,21 @@ namespace dxvk { const DxbcRegisterValue coord = emitRegisterExtract(address, coordArrayMask); + // Fetch texels only if the resource is actually bound + const uint32_t labelMerge = m_module.allocateId(); + const uint32_t labelBound = m_module.allocateId(); + const uint32_t labelUnbound = m_module.allocateId(); + + m_module.opSelectionMerge(labelMerge, spv::SelectionControlMaskNone); + m_module.opBranchConditional(m_textures.at(textureId).specId, labelBound, labelUnbound); + m_module.opLabel(labelBound); + + // Reading a typed image or buffer view + // always returns a four-component vector. const uint32_t imageId = m_module.opLoad( m_textures.at(textureId).imageTypeId, m_textures.at(textureId).varId); - // Reading a typed image or buffer view - // always returns a four-component vector. DxbcRegisterValue result; result.type.ctype = m_textures.at(textureId).sampledType; result.type.ccount = 4; @@ -2226,7 +2240,36 @@ namespace dxvk { // and the destination operand's write mask result = emitRegisterSwizzle(result, ins.src[1].swizzle, ins.dst[0].mask); - emitRegisterStore(ins.dst[0], result); + + // If the texture is not bound, return zeroes + m_module.opBranch(labelMerge); + m_module.opLabel(labelUnbound); + + DxbcRegisterValue zeroes = [&] { + switch (result.type.ctype) { + case DxbcScalarType::Float32: return emitBuildConstVecf32(0.0f, 0.0f, 0.0f, 0.0f, ins.dst[0].mask); + case DxbcScalarType::Uint32: return emitBuildConstVecu32(0u, 0u, 0u, 0u, ins.dst[0].mask); + case DxbcScalarType::Sint32: return emitBuildConstVeci32(0, 0, 0, 0, ins.dst[0].mask); + default: throw DxvkError("DxbcCompiler: Invalid scalar type"); + } + }(); + + m_module.opBranch(labelMerge); + m_module.opLabel(labelMerge); + + // Merge the result with a phi function + const std::array phiLabels = {{ + { result.id, labelBound }, + { zeroes.id, labelUnbound }, + }}; + + DxbcRegisterValue mergedResult; + mergedResult.type = result.type; + mergedResult.id = m_module.opPhi( + getVectorTypeId(mergedResult.type), + phiLabels.size(), phiLabels.data()); + + emitRegisterStore(ins.dst[0], mergedResult); } @@ -2312,16 +2355,6 @@ namespace dxvk { ? m_module.defSampledImageType(m_textures.at(textureId).depthTypeId) : m_module.defSampledImageType(m_textures.at(textureId).colorTypeId); - // Combine the texture and the sampler into a sampled image - const uint32_t sampledImageId = m_module.opSampledImage( - sampledImageType, - m_module.opLoad( - m_textures.at(textureId).imageTypeId, - m_textures.at(textureId).varId), - m_module.opLoad( - m_samplers.at(samplerId).typeId, - m_samplers.at(samplerId).varId)); - // Accumulate additional image operands. These are // not part of the actual operand token in SPIR-V. SpirvImageOperands imageOperands; @@ -2339,6 +2372,25 @@ namespace dxvk { imageLayerDim, offsetIds.data()); } + // Only execute the sample operation if the resource is bound + const uint32_t labelMerge = m_module.allocateId(); + const uint32_t labelBound = m_module.allocateId(); + const uint32_t labelUnbound = m_module.allocateId(); + + m_module.opSelectionMerge(labelMerge, spv::SelectionControlMaskNone); + m_module.opBranchConditional(m_textures.at(textureId).specId, labelBound, labelUnbound); + m_module.opLabel(labelBound); + + // Combine the texture and the sampler into a sampled image + const uint32_t sampledImageId = m_module.opSampledImage( + sampledImageType, + m_module.opLoad( + m_textures.at(textureId).imageTypeId, + m_textures.at(textureId).varId), + m_module.opLoad( + m_samplers.at(samplerId).typeId, + m_samplers.at(samplerId).varId)); + // Sampling an image always returns a four-component // vector, whereas depth-compare ops return a scalar. DxbcRegisterValue result; @@ -2384,7 +2436,6 @@ namespace dxvk { // Sample operation with explicit LOD case DxbcOpcode::SampleL: { - imageOperands.flags |= spv::ImageOperandsLodMask; imageOperands.sLod = explicitLod.id; @@ -2407,7 +2458,35 @@ namespace dxvk { textureReg.swizzle, ins.dst[0].mask); } - emitRegisterStore(ins.dst[0], result); + // If the texture is not bound, return zeroes + m_module.opBranch(labelMerge); + m_module.opLabel(labelUnbound); + + DxbcRegisterValue zeroes = [&] { + switch (result.type.ctype) { + case DxbcScalarType::Float32: return emitBuildConstVecf32(0.0f, 0.0f, 0.0f, 0.0f, ins.dst[0].mask); + case DxbcScalarType::Uint32: return emitBuildConstVecu32(0u, 0u, 0u, 0u, ins.dst[0].mask); + case DxbcScalarType::Sint32: return emitBuildConstVeci32(0, 0, 0, 0, ins.dst[0].mask); + default: throw DxvkError("DxbcCompiler: Invalid scalar type"); + } + }(); + + m_module.opBranch(labelMerge); + m_module.opLabel(labelMerge); + + // Merge the result with a phi function + const std::array phiLabels = {{ + { result.id, labelBound }, + { zeroes.id, labelUnbound }, + }}; + + DxbcRegisterValue mergedResult; + mergedResult.type = result.type; + mergedResult.id = m_module.opPhi( + getVectorTypeId(mergedResult.type), + phiLabels.size(), phiLabels.data()); + + emitRegisterStore(ins.dst[0], mergedResult); } @@ -2850,6 +2929,7 @@ namespace dxvk { float z, float w, const DxbcRegMask& writeMask) { + // TODO refactor these functions into one single template std::array ids = { 0, 0, 0, 0 }; uint32_t componentIndex = 0; @@ -2870,6 +2950,58 @@ namespace dxvk { } + DxbcRegisterValue DxbcCompiler::emitBuildConstVecu32( + uint32_t x, + uint32_t y, + uint32_t z, + uint32_t w, + const DxbcRegMask& writeMask) { + std::array ids = { 0, 0, 0, 0 }; + uint32_t componentIndex = 0; + + if (writeMask[0]) ids[componentIndex++] = m_module.constu32(x); + if (writeMask[1]) ids[componentIndex++] = m_module.constu32(y); + if (writeMask[2]) ids[componentIndex++] = m_module.constu32(z); + if (writeMask[3]) ids[componentIndex++] = m_module.constu32(w); + + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Uint32; + result.type.ccount = componentIndex; + result.id = componentIndex > 1 + ? m_module.constComposite( + getVectorTypeId(result.type), + componentIndex, ids.data()) + : ids[0]; + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitBuildConstVeci32( + int32_t x, + int32_t y, + int32_t z, + int32_t w, + const DxbcRegMask& writeMask) { + std::array ids = { 0, 0, 0, 0 }; + uint32_t componentIndex = 0; + + if (writeMask[0]) ids[componentIndex++] = m_module.consti32(x); + if (writeMask[1]) ids[componentIndex++] = m_module.consti32(y); + if (writeMask[2]) ids[componentIndex++] = m_module.consti32(z); + if (writeMask[3]) ids[componentIndex++] = m_module.consti32(w); + + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Sint32; + result.type.ccount = componentIndex; + result.id = componentIndex > 1 + ? m_module.constComposite( + getVectorTypeId(result.type), + componentIndex, ids.data()) + : ids[0]; + return result; + } + + DxbcRegisterValue DxbcCompiler::emitBuildZero( DxbcScalarType type) { DxbcRegisterValue result; @@ -3668,6 +3800,58 @@ namespace dxvk { } + DxbcRegisterValue DxbcCompiler::emitRegisterLoadRaw( + const DxbcRegister& reg) { + if (reg.type == DxbcOperandType::ConstantBuffer) { + // Constant buffer require special care if they are not bound + const uint32_t registerId = reg.idx[0].offset; + + const uint32_t labelMerge = m_module.allocateId(); + const uint32_t labelBound = m_module.allocateId(); + const uint32_t labelUnbound = m_module.allocateId(); + + m_module.opSelectionMerge(labelMerge, spv::SelectionControlMaskNone); + m_module.opBranchConditional( + m_constantBuffers.at(registerId).specId, + labelBound, labelUnbound); + + // Case 1: Constant buffer is bound. + // Load the register value normally. + m_module.opLabel(labelBound); + DxbcRegisterValue ifBound = emitValueLoad(emitGetOperandPtr(reg)); + m_module.opBranch(labelMerge); + + // Case 2: Constant buffer is not bound. + // Return zeroes unconditionally. + m_module.opLabel(labelUnbound); + DxbcRegisterValue ifUnbound = emitBuildConstVecf32( + 0.0f, 0.0f, 0.0f, 0.0f, + DxbcRegMask(true, true, true, true)); + m_module.opBranch(labelMerge); + + // Merge the results with a phi function + m_module.opLabel(labelMerge); + + const std::array phiLabels = {{ + { ifBound.id, labelBound }, + { ifUnbound.id, labelUnbound }, + }}; + + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Float32; + result.type.ccount = 4; + result.id = m_module.opPhi( + getVectorTypeId(result.type), + phiLabels.size(), + phiLabels.data()); + return result; + } else { + // All other operand types can be accessed directly + return emitValueLoad(emitGetOperandPtr(reg)); + } + } + + DxbcRegisterValue DxbcCompiler::emitRegisterLoad( const DxbcRegister& reg, DxbcRegMask writeMask) { @@ -3710,8 +3894,7 @@ namespace dxvk { return emitRegisterBitcast(result, reg.dataType); } else { // Load operand from the operand pointer - DxbcRegisterPointer ptr = emitGetOperandPtr(reg); - DxbcRegisterValue result = emitValueLoad(ptr); + DxbcRegisterValue result = emitRegisterLoadRaw(reg); // Apply operand swizzle to the operand value result = emitRegisterSwizzle(result, reg.swizzle, writeMask); diff --git a/src/dxbc/dxbc_compiler.h b/src/dxbc/dxbc_compiler.h index 426f7de1f..6bbbcbc60 100644 --- a/src/dxbc/dxbc_compiler.h +++ b/src/dxbc/dxbc_compiler.h @@ -505,6 +505,20 @@ namespace dxvk { float w, const DxbcRegMask& writeMask); + DxbcRegisterValue emitBuildConstVecu32( + uint32_t x, + uint32_t y, + uint32_t z, + uint32_t w, + const DxbcRegMask& writeMask); + + DxbcRegisterValue emitBuildConstVeci32( + int32_t x, + int32_t y, + int32_t z, + int32_t w, + const DxbcRegMask& writeMask); + DxbcRegisterValue emitBuildZero( DxbcScalarType type); @@ -627,6 +641,9 @@ namespace dxvk { DxbcRegisterValue value, DxbcRegMask writeMask); + DxbcRegisterValue emitRegisterLoadRaw( + const DxbcRegister& reg); + DxbcRegisterValue emitRegisterLoad( const DxbcRegister& reg, DxbcRegMask writeMask); diff --git a/src/dxvk/dxvk_context.cpp b/src/dxvk/dxvk_context.cpp index 022884bcc..8a758566c 100644 --- a/src/dxvk/dxvk_context.cpp +++ b/src/dxvk/dxvk_context.cpp @@ -1086,10 +1086,6 @@ namespace dxvk { m_cmd->trackResource(res.sampler); } else { updatePipelineState |= bs.setUnbound(i); - - m_descriptors[i].image.sampler = VK_NULL_HANDLE; - m_descriptors[i].image.imageView = VK_NULL_HANDLE; - m_descriptors[i].image.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED; } break; case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: @@ -1105,10 +1101,6 @@ namespace dxvk { m_cmd->trackResource(res.imageView->image()); } else { updatePipelineState |= bs.setUnbound(i); - - m_descriptors[i].image.sampler = VK_NULL_HANDLE; - m_descriptors[i].image.imageView = VK_NULL_HANDLE; - m_descriptors[i].image.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED; } break; case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: @@ -1122,8 +1114,6 @@ namespace dxvk { m_cmd->trackResource(res.bufferView->buffer()->resource()); } else { updatePipelineState |= bs.setUnbound(i); - - m_descriptors[i].texelBuffer = VK_NULL_HANDLE; } break; case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: @@ -1135,10 +1125,6 @@ namespace dxvk { m_cmd->trackResource(res.bufferSlice.resource()); } else { updatePipelineState |= bs.setUnbound(i); - - m_descriptors[i].buffer.buffer = VK_NULL_HANDLE; - m_descriptors[i].buffer.offset = 0; - m_descriptors[i].buffer.range = 0; } break; default: