|
@@ -15,12 +15,21 @@
|
|
|
#include "string_utils.h"
|
|
#include "string_utils.h"
|
|
|
#include "shaderType.h"
|
|
#include "shaderType.h"
|
|
|
|
|
|
|
|
|
|
+#include "spirVTransformer.h"
|
|
|
|
|
+#include "spirVFlattenStructPass.h"
|
|
|
|
|
+#include "spirVReplaceVariableTypePass.h"
|
|
|
|
|
+#include "spirVRemoveUnusedVariablesPass.h"
|
|
|
|
|
+
|
|
|
#include "GLSL.std.450.h"
|
|
#include "GLSL.std.450.h"
|
|
|
|
|
|
|
|
|
|
+#include <spirv-tools/libspirv.h>
|
|
|
|
|
+
|
|
|
#ifndef NDEBUG
|
|
#ifndef NDEBUG
|
|
|
#include <glslang/SPIRV/disassemble.h>
|
|
#include <glslang/SPIRV/disassemble.h>
|
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
|
|
+using Definition = SpirVResultDatabase::Definition;
|
|
|
|
|
+
|
|
|
TypeHandle ShaderModuleSpirV::_type_handle;
|
|
TypeHandle ShaderModuleSpirV::_type_handle;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -40,8 +49,6 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
|
|
|
}
|
|
}
|
|
|
_record = record;
|
|
_record = record;
|
|
|
|
|
|
|
|
- InstructionWriter writer(_instructions);
|
|
|
|
|
-
|
|
|
|
|
// Check for caps and sanity.
|
|
// Check for caps and sanity.
|
|
|
for (InstructionIterator it = _instructions.begin(); it != _instructions.begin_annotations(); ++it) {
|
|
for (InstructionIterator it = _instructions.begin(); it != _instructions.begin_annotations(); ++it) {
|
|
|
Instruction op = *it;
|
|
Instruction op = *it;
|
|
@@ -123,26 +130,29 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ SpirVTransformer transformer(_instructions);
|
|
|
|
|
+ const SpirVResultDatabase &db = transformer.get_db();
|
|
|
|
|
+
|
|
|
// Check if there is a $Global uniform block. This is generated by the HLSL
|
|
// Check if there is a $Global uniform block. This is generated by the HLSL
|
|
|
// front-end of glslang. If so, unwrap it back down to individual uniforms.
|
|
// front-end of glslang. If so, unwrap it back down to individual uniforms.
|
|
|
- uint32_t type_id = writer.find_definition("$Global");
|
|
|
|
|
|
|
+ uint32_t type_id = db.find_definition("$Global");
|
|
|
if (type_id) {
|
|
if (type_id) {
|
|
|
if (shader_cat.is_spam()) {
|
|
if (shader_cat.is_spam()) {
|
|
|
shader_cat.spam()
|
|
shader_cat.spam()
|
|
|
<< "Flattening $Global uniform block with type " << type_id << "\n";
|
|
<< "Flattening $Global uniform block with type " << type_id << "\n";
|
|
|
}
|
|
}
|
|
|
- writer.flatten_struct(type_id);
|
|
|
|
|
|
|
+ transformer.run(SpirVFlattenStructPass(type_id));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Remove unused variables before assigning locations.
|
|
// Remove unused variables before assigning locations.
|
|
|
- writer.remove_unused_variables();
|
|
|
|
|
|
|
+ transformer.run(SpirVRemoveUnusedVariablesPass());
|
|
|
|
|
|
|
|
// Add in location decorations for any inputs that are missing it.
|
|
// Add in location decorations for any inputs that are missing it.
|
|
|
- writer.assign_locations(stage);
|
|
|
|
|
|
|
+ transformer.assign_locations(stage);
|
|
|
|
|
|
|
|
// Identify the inputs, outputs and uniform parameters.
|
|
// Identify the inputs, outputs and uniform parameters.
|
|
|
- for (uint32_t id = 0; id < _instructions.get_id_bound(); ++id) {
|
|
|
|
|
- const Definition &def = writer.get_definition(id);
|
|
|
|
|
|
|
+ for (uint32_t id = 0; id < transformer.get_id_bound(); ++id) {
|
|
|
|
|
+ const Definition &def = db.get_definition(id);
|
|
|
|
|
|
|
|
if (def.is_used() && def._type != nullptr) {
|
|
if (def.is_used() && def._type != nullptr) {
|
|
|
if (def._type->contains_scalar_type(ShaderType::ST_double)) {
|
|
if (def._type->contains_scalar_type(ShaderType::ST_double)) {
|
|
@@ -158,7 +168,7 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (def._dtype == DT_variable && !def.is_builtin()) {
|
|
|
|
|
|
|
+ if (def.is_variable() && !def.is_builtin()) {
|
|
|
// Ignore empty structs/arrays.
|
|
// Ignore empty structs/arrays.
|
|
|
int num_locations = def._type->get_num_interface_locations();
|
|
int num_locations = def._type->get_num_interface_locations();
|
|
|
if (num_locations == 0) {
|
|
if (num_locations == 0) {
|
|
@@ -199,14 +209,14 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
|
|
|
// a shadow sampler; this isn't always done by the compiler, and the
|
|
// a shadow sampler; this isn't always done by the compiler, and the
|
|
|
// spec isn't clear that this is necessary, but it helps spirv-cross
|
|
// spec isn't clear that this is necessary, but it helps spirv-cross
|
|
|
// properly generate shadow samplers.
|
|
// properly generate shadow samplers.
|
|
|
- if ((def._flags & DF_dref_sampled) != 0 && !sampled_image_type->is_shadow()) {
|
|
|
|
|
|
|
+ if (def.is_dref_sampled() && !sampled_image_type->is_shadow()) {
|
|
|
// No, change the type of this variable.
|
|
// No, change the type of this variable.
|
|
|
var.type = ShaderType::register_type(ShaderType::SampledImage(
|
|
var.type = ShaderType::register_type(ShaderType::SampledImage(
|
|
|
sampled_image_type->get_texture_type(),
|
|
sampled_image_type->get_texture_type(),
|
|
|
sampled_image_type->get_sampled_type(),
|
|
sampled_image_type->get_sampled_type(),
|
|
|
true));
|
|
true));
|
|
|
|
|
|
|
|
- writer.set_variable_type(id, var.type);
|
|
|
|
|
|
|
+ transformer.run(SpirVReplaceVariableTypePass(id, var.type, spv::StorageClassUniformConstant));
|
|
|
}
|
|
}
|
|
|
if (sampled_image_type->get_sampled_type() == ShaderType::ST_uint ||
|
|
if (sampled_image_type->get_sampled_type() == ShaderType::ST_uint ||
|
|
|
sampled_image_type->get_sampled_type() == ShaderType::ST_int) {
|
|
sampled_image_type->get_sampled_type() == ShaderType::ST_int) {
|
|
@@ -227,7 +237,7 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- if (def._flags & DF_dynamically_indexed &&
|
|
|
|
|
|
|
+ if (def.is_dynamically_indexed() &&
|
|
|
(sampled_image_type != nullptr || def._type->contains_opaque_type())) {
|
|
(sampled_image_type != nullptr || def._type->contains_opaque_type())) {
|
|
|
_used_caps |= C_dynamic_indexing;
|
|
_used_caps |= C_dynamic_indexing;
|
|
|
}
|
|
}
|
|
@@ -236,10 +246,10 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
|
|
|
else if (def._storage_class == spv::StorageClassStorageBuffer) {
|
|
else if (def._storage_class == spv::StorageClassStorageBuffer) {
|
|
|
// For whatever reason, in GLSL, the name of an SSBO is derived from the
|
|
// For whatever reason, in GLSL, the name of an SSBO is derived from the
|
|
|
// name of the struct type.
|
|
// name of the struct type.
|
|
|
- const Definition &type_pointer_def = writer.get_definition(def._type_id);
|
|
|
|
|
- nassertd(type_pointer_def._dtype == DT_type_pointer) continue;
|
|
|
|
|
- const Definition &type_def = writer.get_definition(type_pointer_def._type_id);
|
|
|
|
|
- nassertd(type_def._dtype == DT_type) continue;
|
|
|
|
|
|
|
+ const Definition &type_pointer_def = db.get_definition(def._type_id);
|
|
|
|
|
+ nassertd(type_pointer_def.is_pointer_type()) continue;
|
|
|
|
|
+ const Definition &type_def = db.get_definition(type_pointer_def._type_id);
|
|
|
|
|
+ nassertd(type_def.is_type()) continue;
|
|
|
nassertd(!type_def._name.empty()) continue;
|
|
nassertd(!type_def._name.empty()) continue;
|
|
|
|
|
|
|
|
var.name = InternalName::make(type_def._name);
|
|
var.name = InternalName::make(type_def._name);
|
|
@@ -248,7 +258,7 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
|
|
|
_used_caps |= C_storage_buffer;
|
|
_used_caps |= C_storage_buffer;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- else if (def._dtype == DT_variable && def.is_used() &&
|
|
|
|
|
|
|
+ else if (def.is_variable() && def.is_used() &&
|
|
|
def._storage_class == spv::StorageClassInput) {
|
|
def._storage_class == spv::StorageClassInput) {
|
|
|
// Built-in input variable.
|
|
// Built-in input variable.
|
|
|
switch (def._builtin) {
|
|
switch (def._builtin) {
|
|
@@ -298,14 +308,14 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- else if (def._dtype == DT_type && def._type != nullptr) {
|
|
|
|
|
|
|
+ else if (def.is_type() && def._type != nullptr) {
|
|
|
if (const ShaderType::Matrix *matrix_type = def._type->as_matrix()) {
|
|
if (const ShaderType::Matrix *matrix_type = def._type->as_matrix()) {
|
|
|
if (matrix_type->get_num_rows() != matrix_type->get_num_columns()) {
|
|
if (matrix_type->get_num_rows() != matrix_type->get_num_columns()) {
|
|
|
_used_caps |= C_non_square_matrices;
|
|
_used_caps |= C_non_square_matrices;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- else if (def._dtype == DT_spec_constant && def._type != nullptr) {
|
|
|
|
|
|
|
+ else if (def.is_spec_constant() && def._type != nullptr) {
|
|
|
SpecializationConstant spec_constant;
|
|
SpecializationConstant spec_constant;
|
|
|
spec_constant.id = def._spec_id;
|
|
spec_constant.id = def._spec_id;
|
|
|
spec_constant.name = InternalName::make(def._name);
|
|
spec_constant.name = InternalName::make(def._name);
|
|
@@ -319,6 +329,8 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ _instructions = transformer.get_result();
|
|
|
|
|
+
|
|
|
#ifndef NDEBUG
|
|
#ifndef NDEBUG
|
|
|
if (shader_cat.is_spam()) {
|
|
if (shader_cat.is_spam()) {
|
|
|
_instructions.disassemble(shader_cat.spam()
|
|
_instructions.disassemble(shader_cat.spam()
|
|
@@ -330,11 +342,15 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
|
|
|
// from the module.
|
|
// from the module.
|
|
|
strip();
|
|
strip();
|
|
|
|
|
|
|
|
|
|
+#ifndef NDEBUG
|
|
|
|
|
+ _instructions.validate();
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
// Check for more caps, now that we've optimized the module.
|
|
// Check for more caps, now that we've optimized the module.
|
|
|
for (InstructionIterator it = _instructions.begin_annotations(); it != _instructions.end_annotations(); ++it) {
|
|
for (InstructionIterator it = _instructions.begin_annotations(); it != _instructions.end_annotations(); ++it) {
|
|
|
Instruction op = *it;
|
|
Instruction op = *it;
|
|
|
if (op.opcode == spv::OpDecorate) {
|
|
if (op.opcode == spv::OpDecorate) {
|
|
|
- if (writer.get_definition(op.args[0]).is_builtin()) {
|
|
|
|
|
|
|
+ if (db.get_definition(op.args[0]).is_builtin()) {
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
switch ((spv::Decoration)op.args[1]) {
|
|
switch ((spv::Decoration)op.args[1]) {
|
|
@@ -364,8 +380,8 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
|
|
|
switch (op.opcode) {
|
|
switch (op.opcode) {
|
|
|
case spv::OpExtInst:
|
|
case spv::OpExtInst:
|
|
|
{
|
|
{
|
|
|
- const Definition &def = writer.get_definition(op.args[2]);
|
|
|
|
|
- nassertv(def._dtype == DT_ext_inst);
|
|
|
|
|
|
|
+ const Definition &def = db.get_definition(op.args[2]);
|
|
|
|
|
+ nassertv(def.is_ext_inst());
|
|
|
if (def._name == "GLSL.std.450" && op.args[3] == GLSLstd450RoundEven) {
|
|
if (def._name == "GLSL.std.450" && op.args[3] == GLSLstd450RoundEven) {
|
|
|
// We mark the use of the GLSL roundEven() function, which requires
|
|
// We mark the use of the GLSL roundEven() function, which requires
|
|
|
// GLSL 1.30 or HLSL SM 4.0.
|
|
// GLSL 1.30 or HLSL SM 4.0.
|
|
@@ -412,7 +428,7 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
|
|
|
_used_caps |= C_shadow_samplers;
|
|
_used_caps |= C_shadow_samplers;
|
|
|
|
|
|
|
|
{
|
|
{
|
|
|
- const Definition &sampler_def = writer.get_definition(op.args[2]);
|
|
|
|
|
|
|
+ const Definition &sampler_def = db.get_definition(op.args[2]);
|
|
|
if (sampler_def._type != nullptr) {
|
|
if (sampler_def._type != nullptr) {
|
|
|
const ShaderType::SampledImage *sampler = sampler_def._type->as_sampled_image();
|
|
const ShaderType::SampledImage *sampler = sampler_def._type->as_sampled_image();
|
|
|
if (sampler != nullptr &&
|
|
if (sampler != nullptr &&
|
|
@@ -436,7 +452,7 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
|
|
|
case spv::OpImageQuerySizeLod:
|
|
case spv::OpImageQuerySizeLod:
|
|
|
case spv::OpImageQuerySize:
|
|
case spv::OpImageQuerySize:
|
|
|
{
|
|
{
|
|
|
- const Definition &image_def = writer.get_definition(op.args[2]);
|
|
|
|
|
|
|
+ const Definition &image_def = db.get_definition(op.args[2]);
|
|
|
if (image_def._type != nullptr && image_def._type->as_image() != nullptr) {
|
|
if (image_def._type != nullptr && image_def._type->as_image() != nullptr) {
|
|
|
_used_caps |= C_image_query_size;
|
|
_used_caps |= C_image_query_size;
|
|
|
} else {
|
|
} else {
|
|
@@ -448,8 +464,8 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
|
|
|
case spv::OpImageGather:
|
|
case spv::OpImageGather:
|
|
|
case spv::OpImageSparseGather:
|
|
case spv::OpImageSparseGather:
|
|
|
{
|
|
{
|
|
|
- const Definition &component = writer.get_definition(op.args[4]);
|
|
|
|
|
- if (component._dtype == DT_constant && component._constant == 0) {
|
|
|
|
|
|
|
+ const Definition &component = db.get_definition(op.args[4]);
|
|
|
|
|
+ if (component.is_constant() && component._constant == 0) {
|
|
|
_used_caps |= C_texture_gather_red;
|
|
_used_caps |= C_texture_gather_red;
|
|
|
} else {
|
|
} else {
|
|
|
_used_caps |= C_texture_gather_any;
|
|
_used_caps |= C_texture_gather_any;
|
|
@@ -640,6 +656,29 @@ validate_header() const {
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Checks whether this is valid SPIR-V.
|
|
|
|
|
+ */
|
|
|
|
|
+bool ShaderModuleSpirV::InstructionStream::
|
|
|
|
|
+validate() const {
|
|
|
|
|
+ spv_context context = spvContextCreate(SPV_ENV_UNIVERSAL_1_0);
|
|
|
|
|
+ spv_const_binary_t binary = {_words.data(), _words.size()};
|
|
|
|
|
+ spv_diagnostic diagnostic = nullptr;
|
|
|
|
|
+
|
|
|
|
|
+ spv_result_t result = spvValidate(context, &binary, &diagnostic);
|
|
|
|
|
+
|
|
|
|
|
+ if (diagnostic != nullptr) {
|
|
|
|
|
+ shader_cat.error()
|
|
|
|
|
+ << "SPIR-V validation failed:\n" << diagnostic->error << "\n";
|
|
|
|
|
+
|
|
|
|
|
+ disassemble(shader_cat.error() << "Disassembly follows:\n");
|
|
|
|
|
+ }
|
|
|
|
|
+ spvDiagnosticDestroy(diagnostic);
|
|
|
|
|
+ spvContextDestroy(context);
|
|
|
|
|
+
|
|
|
|
|
+ return result == SPV_SUCCESS;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Writes a disassembly, for debug purposes. Returns false if the disassembler
|
|
* Writes a disassembly, for debug purposes. Returns false if the disassembler
|
|
|
* is disabled, eg. in a release build.
|
|
* is disabled, eg. in a release build.
|
|
@@ -860,2547 +899,3 @@ fillin(DatagramIterator &scan, BamReader *manager) {
|
|
|
_instructions = std::move(words);
|
|
_instructions = std::move(words);
|
|
|
nassertv(_instructions.validate_header());
|
|
nassertv(_instructions.validate_header());
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Returns true if this type contains anything decorated with BuiltIn.
|
|
|
|
|
- */
|
|
|
|
|
-bool ShaderModuleSpirV::Definition::
|
|
|
|
|
-has_builtin() const {
|
|
|
|
|
- if (_builtin != spv::BuiltInMax) {
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
- for (const MemberDefinition &def : _members) {
|
|
|
|
|
- if (def._builtin != spv::BuiltInMax) {
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return false;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Returns a MemberDefinition for the given member.
|
|
|
|
|
- */
|
|
|
|
|
-const ShaderModuleSpirV::MemberDefinition &ShaderModuleSpirV::Definition::
|
|
|
|
|
-get_member(uint32_t i) const {
|
|
|
|
|
- static MemberDefinition default_def;
|
|
|
|
|
- if (i >= _members.size()) {
|
|
|
|
|
- return default_def;
|
|
|
|
|
- }
|
|
|
|
|
- return _members[i];
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Returns a modifiable MemberDefinition for the given member.
|
|
|
|
|
- */
|
|
|
|
|
-ShaderModuleSpirV::MemberDefinition &ShaderModuleSpirV::Definition::
|
|
|
|
|
-modify_member(uint32_t i) {
|
|
|
|
|
- if (i >= _members.size()) {
|
|
|
|
|
- _members.resize(i + 1);
|
|
|
|
|
- }
|
|
|
|
|
- return _members[i];
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Clears this definition, in case it has just been removed.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::Definition::
|
|
|
|
|
-clear() {
|
|
|
|
|
- _dtype = DT_none;
|
|
|
|
|
- _name.clear();
|
|
|
|
|
- _type = nullptr;
|
|
|
|
|
- _location = -1;
|
|
|
|
|
- _builtin = spv::BuiltInMax;
|
|
|
|
|
- _constant = 0;
|
|
|
|
|
- _type_id = 0;
|
|
|
|
|
- _array_stride = 0;
|
|
|
|
|
- _origin_id = 0;
|
|
|
|
|
- _function_id = 0;
|
|
|
|
|
- _members.clear();
|
|
|
|
|
- _flags = 0;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Constructs an instruction writer to operate on the given instruction stream.
|
|
|
|
|
- */
|
|
|
|
|
-ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-InstructionWriter(InstructionStream &stream) :
|
|
|
|
|
- _instructions(stream),
|
|
|
|
|
- _defs(_instructions.get_id_bound()) {
|
|
|
|
|
-
|
|
|
|
|
- uint32_t current_function_id = 0;
|
|
|
|
|
- for (Instruction op : _instructions) {
|
|
|
|
|
- parse_instruction(op, current_function_id);
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Finds the definition with the given name.
|
|
|
|
|
- */
|
|
|
|
|
-uint32_t ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-find_definition(const std::string &name) const {
|
|
|
|
|
- for (uint32_t id = 0; id < _defs.size(); ++id) {
|
|
|
|
|
- if (_defs[id]._name == name) {
|
|
|
|
|
- return id;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return 0;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Returns the definition by its identifier.
|
|
|
|
|
- */
|
|
|
|
|
-const ShaderModuleSpirV::Definition &ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-get_definition(uint32_t id) const {
|
|
|
|
|
- if (id >= _defs.size()) {
|
|
|
|
|
- static Definition default_def;
|
|
|
|
|
- return default_def;
|
|
|
|
|
- }
|
|
|
|
|
- return _defs[id];
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Returns a mutable definition by its identifier. May invalidate existing
|
|
|
|
|
- * definition references.
|
|
|
|
|
- */
|
|
|
|
|
-ShaderModuleSpirV::Definition &ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-modify_definition(uint32_t id) {
|
|
|
|
|
- if (id >= _defs.size()) {
|
|
|
|
|
- _defs.resize(id + 1);
|
|
|
|
|
- }
|
|
|
|
|
- return _defs[id];
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Assigns location decorations to all input, output and uniform variables that
|
|
|
|
|
- * do not have a location decoration yet.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-assign_locations(Stage stage) {
|
|
|
|
|
- // Determine which locations have already been assigned.
|
|
|
|
|
- bool has_unassigned_locations = false;
|
|
|
|
|
- BitArray input_locations;
|
|
|
|
|
- BitArray output_locations;
|
|
|
|
|
- BitArray uniform_locations;
|
|
|
|
|
-
|
|
|
|
|
- for (const Definition &def : _defs) {
|
|
|
|
|
- if (def._dtype == DT_variable) {
|
|
|
|
|
- if (!def.has_location()) {
|
|
|
|
|
- if (!def.is_builtin() &&
|
|
|
|
|
- (def._storage_class == spv::StorageClassInput ||
|
|
|
|
|
- def._storage_class == spv::StorageClassOutput ||
|
|
|
|
|
- def._storage_class == spv::StorageClassUniformConstant)) {
|
|
|
|
|
- // A non-built-in variable definition without a location.
|
|
|
|
|
- has_unassigned_locations = true;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- else if (def._storage_class == spv::StorageClassInput) {
|
|
|
|
|
- input_locations.set_range(def._location, def._type ? def._type->get_num_interface_locations() : 1);
|
|
|
|
|
- }
|
|
|
|
|
- else if (def._storage_class == spv::StorageClassOutput) {
|
|
|
|
|
- output_locations.set_range(def._location, def._type ? def._type->get_num_interface_locations() : 1);
|
|
|
|
|
- }
|
|
|
|
|
- /*else if (def._storage_class == spv::StorageClassUniformConstant) {
|
|
|
|
|
- uniform_locations.set_range(def._location, def._type ? def._type->get_num_parameter_locations() : 1);
|
|
|
|
|
- }*/
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (!has_unassigned_locations) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Insert decorations for every unassigned variable at the beginning of the
|
|
|
|
|
- // annotations block.
|
|
|
|
|
- InstructionIterator it = _instructions.begin_annotations();
|
|
|
|
|
- for (uint32_t id = 0; id < _defs.size(); ++id) {
|
|
|
|
|
- Definition &def = _defs[id];
|
|
|
|
|
- if (def._dtype == DT_variable && !def.has_location() && !def.is_builtin()) {
|
|
|
|
|
- int location;
|
|
|
|
|
- int num_locations;
|
|
|
|
|
- const char *sc_str;
|
|
|
|
|
-
|
|
|
|
|
- if (def._storage_class == spv::StorageClassInput) {
|
|
|
|
|
- num_locations = def._type->get_num_interface_locations();
|
|
|
|
|
- if (num_locations == 0) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (stage == Stage::vertex && !input_locations.get_bit(0) &&
|
|
|
|
|
- def._name != "vertex" && def._name != "p3d_Vertex" &&
|
|
|
|
|
- def._name != "vtx_position") {
|
|
|
|
|
- // Leave location 0 open for the vertex attribute.
|
|
|
|
|
- location = input_locations.find_off_range(num_locations, 1);
|
|
|
|
|
- } else {
|
|
|
|
|
- location = input_locations.find_off_range(num_locations);
|
|
|
|
|
- }
|
|
|
|
|
- input_locations.set_range(location, num_locations);
|
|
|
|
|
-
|
|
|
|
|
- sc_str = "input";
|
|
|
|
|
- }
|
|
|
|
|
- else if (def._storage_class == spv::StorageClassOutput) {
|
|
|
|
|
- num_locations = def._type->get_num_interface_locations();
|
|
|
|
|
- if (num_locations == 0) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- location = output_locations.find_off_range(num_locations);
|
|
|
|
|
- output_locations.set_range(location, num_locations);
|
|
|
|
|
-
|
|
|
|
|
- sc_str = "output";
|
|
|
|
|
- }
|
|
|
|
|
- /*else if (def._storage_class == spv::StorageClassUniformConstant) {
|
|
|
|
|
- num_locations = def._type->get_num_parameter_locations();
|
|
|
|
|
- if (num_locations == 0) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- location = uniform_locations.find_off_range(num_locations);
|
|
|
|
|
- uniform_locations.set_range(location, num_locations);
|
|
|
|
|
-
|
|
|
|
|
- sc_str = "uniform";
|
|
|
|
|
- }*/
|
|
|
|
|
- else {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- nassertd(location >= 0) continue;
|
|
|
|
|
-
|
|
|
|
|
- if (shader_cat.is_debug()) {
|
|
|
|
|
- if (num_locations == 1) {
|
|
|
|
|
- shader_cat.debug()
|
|
|
|
|
- << "Assigning " << def._name << " to " << sc_str << " location "
|
|
|
|
|
- << location << "\n";
|
|
|
|
|
- } else {
|
|
|
|
|
- shader_cat.debug()
|
|
|
|
|
- << "Assigning " << def._name << " to " << sc_str << " locations "
|
|
|
|
|
- << location << ".." << (location + num_locations - 1) << "\n";
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- def._location = location;
|
|
|
|
|
- it = _instructions.insert(it,
|
|
|
|
|
- spv::OpDecorate, {id, spv::DecorationLocation, (uint32_t)location});
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Assigns location decorations based on the given remapping.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-assign_locations(pmap<uint32_t, int> remap) {
|
|
|
|
|
- // Replace existing locations.
|
|
|
|
|
- InstructionIterator it = _instructions.begin_annotations();
|
|
|
|
|
- while (it != _instructions.end_annotations()) {
|
|
|
|
|
- Instruction op = *it;
|
|
|
|
|
-
|
|
|
|
|
- if (op.opcode == spv::OpDecorate &&
|
|
|
|
|
- (spv::Decoration)op.args[1] == spv::DecorationLocation && op.nargs >= 3) {
|
|
|
|
|
- auto it = remap.find(op.args[0]);
|
|
|
|
|
- if (it != remap.end()) {
|
|
|
|
|
- op.args[2] = it->second;
|
|
|
|
|
- remap.erase(it);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Insert decorations for every unassigned variable at the beginning of the
|
|
|
|
|
- // annotations block.
|
|
|
|
|
- if (!remap.empty()) {
|
|
|
|
|
- it = _instructions.begin_annotations();
|
|
|
|
|
- for (auto rit = remap.begin(); rit != remap.end(); ++rit) {
|
|
|
|
|
- it = _instructions.insert(it,
|
|
|
|
|
- spv::OpDecorate, {rit->first, spv::DecorationLocation, (uint32_t)rit->second});
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Assign descriptor bindings for a descriptor set based on the given locations.
|
|
|
|
|
- * Assumes there are already binding and set decorations.
|
|
|
|
|
- * To create gaps in the descriptor set, entries in locations may be -1.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-bind_descriptor_set(uint32_t set, const vector_int &locations) {
|
|
|
|
|
- for (InstructionIterator it = _instructions.begin_annotations();
|
|
|
|
|
- it != _instructions.end() && (*it).is_annotation();
|
|
|
|
|
- ++it) {
|
|
|
|
|
- Instruction op = *it;
|
|
|
|
|
-
|
|
|
|
|
- if (op.opcode == spv::OpDecorate && op.nargs >= 3) {
|
|
|
|
|
- Definition &def = _defs[op.args[0]];
|
|
|
|
|
-
|
|
|
|
|
- auto lit = std::find(locations.begin(), locations.end(), def._location);
|
|
|
|
|
- if (lit != locations.end() && def.has_location()) {
|
|
|
|
|
- if (op.args[1] == spv::DecorationBinding) {
|
|
|
|
|
- op.args[2] = std::distance(locations.begin(), lit);
|
|
|
|
|
- }
|
|
|
|
|
- else if (op.args[1] == spv::DecorationDescriptorSet) {
|
|
|
|
|
- op.args[2] = set;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Removes unused variables.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-remove_unused_variables() {
|
|
|
|
|
- pset<uint32_t> delete_ids;
|
|
|
|
|
-
|
|
|
|
|
- for (uint32_t id = 0; id < _instructions.get_id_bound(); ++id) {
|
|
|
|
|
- Definition &def = modify_definition(id);
|
|
|
|
|
-
|
|
|
|
|
- if (def._dtype == DT_variable && !def.is_used()) {
|
|
|
|
|
- delete_ids.insert(id);
|
|
|
|
|
- if (shader_cat.is_debug() && !def._name.empty()) {
|
|
|
|
|
- shader_cat.debug()
|
|
|
|
|
- << "Removing unused variable " << def._name << " (" << id << ")\n";
|
|
|
|
|
- }
|
|
|
|
|
- def.clear();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (delete_ids.empty()) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- InstructionIterator it = _instructions.begin();
|
|
|
|
|
- while (it != _instructions.end()) {
|
|
|
|
|
- Instruction op = *it;
|
|
|
|
|
-
|
|
|
|
|
- switch (op.opcode) {
|
|
|
|
|
- case spv::OpEntryPoint:
|
|
|
|
|
- {
|
|
|
|
|
- // Skip the string literal by skipping words until we have a zero byte.
|
|
|
|
|
- uint32_t i = 2;
|
|
|
|
|
- while (i < op.nargs
|
|
|
|
|
- && (op.args[i] & 0x000000ff) != 0
|
|
|
|
|
- && (op.args[i] & 0x0000ff00) != 0
|
|
|
|
|
- && (op.args[i] & 0x00ff0000) != 0
|
|
|
|
|
- && (op.args[i] & 0xff000000) != 0) {
|
|
|
|
|
- ++i;
|
|
|
|
|
- }
|
|
|
|
|
- ++i;
|
|
|
|
|
- // Remove the deleted IDs from the entry point interface.
|
|
|
|
|
- while (i < (*it).nargs) {
|
|
|
|
|
- if (delete_ids.count((*it).args[i])) {
|
|
|
|
|
- it = _instructions.erase_arg(it, i);
|
|
|
|
|
- } else {
|
|
|
|
|
- ++i;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpName:
|
|
|
|
|
- case spv::OpMemberName:
|
|
|
|
|
- case spv::OpDecorate:
|
|
|
|
|
- case spv::OpDecorateId:
|
|
|
|
|
- case spv::OpDecorateString:
|
|
|
|
|
- case spv::OpMemberDecorate:
|
|
|
|
|
- case spv::OpMemberDecorateString:
|
|
|
|
|
- // Delete decorations on the variable.
|
|
|
|
|
- if (op.nargs >= 1 && delete_ids.count(op.args[0])) {
|
|
|
|
|
- it = _instructions.erase(it);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpVariable:
|
|
|
|
|
- if (op.nargs >= 2 && delete_ids.count(op.args[1])) {
|
|
|
|
|
- it = _instructions.erase(it);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpImageTexelPointer:
|
|
|
|
|
- case spv::OpAccessChain:
|
|
|
|
|
- case spv::OpInBoundsAccessChain:
|
|
|
|
|
- case spv::OpPtrAccessChain:
|
|
|
|
|
- case spv::OpInBoundsPtrAccessChain:
|
|
|
|
|
- case spv::OpCopyObject:
|
|
|
|
|
- case spv::OpBitcast:
|
|
|
|
|
- case spv::OpCopyLogical:
|
|
|
|
|
- // Delete these uses of unused variable
|
|
|
|
|
- if (delete_ids.count(op.args[2])) {
|
|
|
|
|
- delete_ids.insert(op.args[1]);
|
|
|
|
|
- it = _instructions.erase(it);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- default:
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Converts the members of the struct type with the given ID to regular
|
|
|
|
|
- * variables. Useful for unwrapping uniform blocks.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-flatten_struct(uint32_t type_id) {
|
|
|
|
|
- const ShaderType::Struct *struct_type;
|
|
|
|
|
- DCAST_INTO_V(struct_type, _defs[type_id]._type);
|
|
|
|
|
-
|
|
|
|
|
- // Contains the ID of the struct type, variable, and any dependencies.
|
|
|
|
|
- pset<uint32_t> deleted_ids;
|
|
|
|
|
-
|
|
|
|
|
- // Maps access chains accessing struct members to the created variable IDs
|
|
|
|
|
- // for that struct member.
|
|
|
|
|
- pmap<uint32_t, uint32_t> deleted_access_chains;
|
|
|
|
|
-
|
|
|
|
|
- // Collect type pointers that we have to create.
|
|
|
|
|
- pvector<uint32_t> insert_type_pointers;
|
|
|
|
|
-
|
|
|
|
|
- // Holds the new variable IDs for each of the struct members.
|
|
|
|
|
- pvector<uint32_t> member_ids(struct_type->get_num_members());
|
|
|
|
|
-
|
|
|
|
|
- InstructionIterator it = _instructions.begin();
|
|
|
|
|
- while (it != _instructions.end()) {
|
|
|
|
|
- Instruction op = *it;
|
|
|
|
|
-
|
|
|
|
|
- switch (op.opcode) {
|
|
|
|
|
- case spv::OpName:
|
|
|
|
|
- case spv::OpMemberName:
|
|
|
|
|
- case spv::OpDecorate:
|
|
|
|
|
- case spv::OpMemberDecorate:
|
|
|
|
|
- case spv::OpDecorateId:
|
|
|
|
|
- case spv::OpDecorateString:
|
|
|
|
|
- case spv::OpMemberDecorateString:
|
|
|
|
|
- // Delete decorations on the struct type.
|
|
|
|
|
- if (op.nargs >= 1 && op.args[0] == type_id) {
|
|
|
|
|
- it = _instructions.erase(it);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpTypeStruct:
|
|
|
|
|
- // Delete the struct definition itself.
|
|
|
|
|
- if (op.nargs >= 1 && op.args[0] == type_id) {
|
|
|
|
|
- _defs[type_id].clear();
|
|
|
|
|
- it = _instructions.erase(it);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpTypePointer:
|
|
|
|
|
- if (op.nargs >= 3 && op.args[2] == type_id) {
|
|
|
|
|
- // Remember this pointer.
|
|
|
|
|
- deleted_ids.insert(op.args[0]);
|
|
|
|
|
- _defs[op.args[0]].clear();
|
|
|
|
|
- it = _instructions.erase(it);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpVariable:
|
|
|
|
|
- if (op.nargs >= 3 && deleted_ids.count(op.args[0])) {
|
|
|
|
|
- // Delete this variable entirely, and replace it instead with individual
|
|
|
|
|
- // variable definitions for all its members.
|
|
|
|
|
- uint32_t struct_var_id = op.args[1];
|
|
|
|
|
- int struct_location = _defs[struct_var_id]._location;
|
|
|
|
|
- deleted_ids.insert(struct_var_id);
|
|
|
|
|
- it = _instructions.erase(it);
|
|
|
|
|
-
|
|
|
|
|
- std::string struct_var_name = std::move(_defs[struct_var_id]._name);
|
|
|
|
|
- if (shader_cat.is_spam()) {
|
|
|
|
|
- shader_cat.spam()
|
|
|
|
|
- << "Removing variable " << struct_var_id << ": "
|
|
|
|
|
- << *_defs[struct_var_id]._type << " " << struct_var_name << "\n";
|
|
|
|
|
- }
|
|
|
|
|
- _defs[struct_var_id].clear();
|
|
|
|
|
-
|
|
|
|
|
- for (size_t mi = 0; mi < struct_type->get_num_members(); ++mi) {
|
|
|
|
|
- const ShaderType::Struct::Member &member = struct_type->get_member(mi);
|
|
|
|
|
-
|
|
|
|
|
- // Insert a new variable for this struct member.
|
|
|
|
|
- uint32_t variable_id = r_define_variable(it, member.type, spv::StorageClassUniformConstant);
|
|
|
|
|
-
|
|
|
|
|
- Definition &variable_def = modify_definition(variable_id);
|
|
|
|
|
- if (struct_var_name.empty()) {
|
|
|
|
|
- variable_def._name = member.name;
|
|
|
|
|
- } else {
|
|
|
|
|
- variable_def._name = struct_var_name + "." + member.name;
|
|
|
|
|
- }
|
|
|
|
|
- if (struct_location >= 0) {
|
|
|
|
|
- // Assign decorations to the individual members.
|
|
|
|
|
- int location = struct_location + mi;
|
|
|
|
|
- variable_def._location = location;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- member_ids[mi] = variable_id;
|
|
|
|
|
- }
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpAccessChain:
|
|
|
|
|
- case spv::OpInBoundsAccessChain:
|
|
|
|
|
- case spv::OpPtrAccessChain:
|
|
|
|
|
- case spv::OpInBoundsPtrAccessChain:
|
|
|
|
|
- if (deleted_ids.count(op.args[2])) {
|
|
|
|
|
- uint32_t index = _defs[op.args[3]]._constant;
|
|
|
|
|
- if (op.nargs > 4) {
|
|
|
|
|
- // Just unwrap the first index.
|
|
|
|
|
- op.args[2] = member_ids[index];
|
|
|
|
|
- it = _instructions.erase_arg(it, 3);
|
|
|
|
|
-
|
|
|
|
|
- // We also need to change the type if it has the wrong storage class.
|
|
|
|
|
- const Definition &typeptr_def = get_definition(op.args[0]);
|
|
|
|
|
- nassertv(typeptr_def._dtype == DT_type_pointer);
|
|
|
|
|
-
|
|
|
|
|
- uint32_t type_pointer_id = find_type_pointer(typeptr_def._type, spv::StorageClassUniformConstant);
|
|
|
|
|
- if (type_pointer_id == 0) {
|
|
|
|
|
- // Can't create the type pointer immediately, since we're no longer
|
|
|
|
|
- // in the type declaration block. We'll add it at the end.
|
|
|
|
|
- type_pointer_id = _instructions.allocate_id();
|
|
|
|
|
- record_type_pointer(type_pointer_id, spv::StorageClassUniformConstant, typeptr_def._type_id);
|
|
|
|
|
- insert_type_pointers.push_back(type_pointer_id);
|
|
|
|
|
- }
|
|
|
|
|
- op.args[0] = type_pointer_id;
|
|
|
|
|
-
|
|
|
|
|
- // Change the origin so that future loads through this access chain
|
|
|
|
|
- // will be able to mark the new variable as used.
|
|
|
|
|
- Definition &def = modify_definition(op.args[1]);
|
|
|
|
|
- def._type_id = type_pointer_id;
|
|
|
|
|
- def._origin_id = op.args[2];
|
|
|
|
|
- } else {
|
|
|
|
|
- // Delete the access chain entirely.
|
|
|
|
|
- deleted_access_chains[op.args[1]] = member_ids[index];
|
|
|
|
|
- it = _instructions.erase(it);
|
|
|
|
|
-
|
|
|
|
|
- _defs[op.args[1]].clear();
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- else if (deleted_access_chains.count(op.args[2])) {
|
|
|
|
|
- // The base of this access chain is an access chain we deleted.
|
|
|
|
|
- op.args[2] = deleted_access_chains[op.args[2]];
|
|
|
|
|
-
|
|
|
|
|
- const Definition &typeptr_def = get_definition(op.args[0]);
|
|
|
|
|
- nassertv(typeptr_def._dtype == DT_type_pointer);
|
|
|
|
|
- uint32_t type_pointer_id = find_type_pointer(typeptr_def._type, spv::StorageClassUniformConstant);
|
|
|
|
|
- if (type_pointer_id == 0) {
|
|
|
|
|
- type_pointer_id = _instructions.allocate_id();
|
|
|
|
|
- record_type_pointer(type_pointer_id, spv::StorageClassUniformConstant, typeptr_def._type_id);
|
|
|
|
|
- insert_type_pointers.push_back(type_pointer_id);
|
|
|
|
|
- }
|
|
|
|
|
- op.args[0] = type_pointer_id;
|
|
|
|
|
-
|
|
|
|
|
- Definition &def = modify_definition(op.args[1]);
|
|
|
|
|
- def._type_id = type_pointer_id;
|
|
|
|
|
- def._origin_id = op.args[2];
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpFunctionCall:
|
|
|
|
|
- for (size_t i = 3; i < op.nargs; ++i) {
|
|
|
|
|
- if (deleted_access_chains.count(op.args[i])) {
|
|
|
|
|
- op.args[i] = deleted_access_chains[op.args[i]];
|
|
|
|
|
- }
|
|
|
|
|
- mark_used(op.args[i]);
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpImageTexelPointer:
|
|
|
|
|
- case spv::OpLoad:
|
|
|
|
|
- case spv::OpAtomicLoad:
|
|
|
|
|
- case spv::OpAtomicExchange:
|
|
|
|
|
- case spv::OpAtomicCompareExchange:
|
|
|
|
|
- case spv::OpAtomicCompareExchangeWeak:
|
|
|
|
|
- case spv::OpAtomicIIncrement:
|
|
|
|
|
- case spv::OpAtomicIDecrement:
|
|
|
|
|
- case spv::OpAtomicIAdd:
|
|
|
|
|
- case spv::OpAtomicISub:
|
|
|
|
|
- case spv::OpAtomicSMin:
|
|
|
|
|
- case spv::OpAtomicUMin:
|
|
|
|
|
- case spv::OpAtomicSMax:
|
|
|
|
|
- case spv::OpAtomicUMax:
|
|
|
|
|
- case spv::OpAtomicAnd:
|
|
|
|
|
- case spv::OpAtomicOr:
|
|
|
|
|
- case spv::OpAtomicXor:
|
|
|
|
|
- case spv::OpAtomicFlagTestAndSet:
|
|
|
|
|
- case spv::OpAtomicFMinEXT:
|
|
|
|
|
- case spv::OpAtomicFMaxEXT:
|
|
|
|
|
- case spv::OpAtomicFAddEXT:
|
|
|
|
|
- // If this triggers, the struct is being loaded into another variable,
|
|
|
|
|
- // which means we can't unwrap this (for now).
|
|
|
|
|
- nassertv(!deleted_ids.count(op.args[2]));
|
|
|
|
|
-
|
|
|
|
|
- if (deleted_access_chains.count(op.args[2])) {
|
|
|
|
|
- op.args[2] = deleted_access_chains[op.args[2]];
|
|
|
|
|
-
|
|
|
|
|
- Definition &def = modify_definition(op.args[1]);
|
|
|
|
|
- def._origin_id = op.args[2];
|
|
|
|
|
- }
|
|
|
|
|
- else if (deleted_ids.count(_defs[op.args[1]]._origin_id)) {
|
|
|
|
|
- // Origin points to deleted variable, change to proper variable.
|
|
|
|
|
- const Definition &from = get_definition(op.args[2]);
|
|
|
|
|
- Definition &def = modify_definition(op.args[1]);
|
|
|
|
|
- def._origin_id = from._origin_id;
|
|
|
|
|
- }
|
|
|
|
|
- mark_used(op.args[1]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpStore:
|
|
|
|
|
- case spv::OpAtomicStore:
|
|
|
|
|
- case spv::OpAtomicFlagClear:
|
|
|
|
|
- // Can't store the struct pointer itself (yet)
|
|
|
|
|
- nassertv(!deleted_ids.count(op.args[0]));
|
|
|
|
|
-
|
|
|
|
|
- if (deleted_access_chains.count(op.args[0])) {
|
|
|
|
|
- op.args[0] = deleted_access_chains[op.args[0]];
|
|
|
|
|
- }
|
|
|
|
|
- mark_used(op.args[0]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpCopyMemory:
|
|
|
|
|
- case spv::OpCopyMemorySized:
|
|
|
|
|
- // Shouldn't be copying into or out of the struct directly.
|
|
|
|
|
- nassertv(!deleted_ids.count(op.args[0]));
|
|
|
|
|
- nassertv(!deleted_ids.count(op.args[1]));
|
|
|
|
|
-
|
|
|
|
|
- if (deleted_access_chains.count(op.args[0])) {
|
|
|
|
|
- op.args[0] = deleted_access_chains[op.args[0]];
|
|
|
|
|
- }
|
|
|
|
|
- if (deleted_access_chains.count(op.args[1])) {
|
|
|
|
|
- op.args[1] = deleted_access_chains[op.args[1]];
|
|
|
|
|
- }
|
|
|
|
|
- mark_used(op.args[0]);
|
|
|
|
|
- mark_used(op.args[1]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpArrayLength:
|
|
|
|
|
- case spv::OpConvertPtrToU:
|
|
|
|
|
- nassertv(!deleted_ids.count(op.args[2]));
|
|
|
|
|
-
|
|
|
|
|
- if (deleted_access_chains.count(op.args[2])) {
|
|
|
|
|
- op.args[2] = deleted_access_chains[op.args[2]];
|
|
|
|
|
- }
|
|
|
|
|
- mark_used(op.args[2]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpCopyObject:
|
|
|
|
|
- if (deleted_ids.count(op.args[2])) {
|
|
|
|
|
- // If it's just a copy of the struct pointer, delete the copy.
|
|
|
|
|
- deleted_ids.insert(op.args[1]);
|
|
|
|
|
- _defs[op.args[1]].clear();
|
|
|
|
|
- it = _instructions.erase(it);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- else if (deleted_access_chains.count(op.args[2])) {
|
|
|
|
|
- op.args[2] = deleted_access_chains[op.args[2]];
|
|
|
|
|
-
|
|
|
|
|
- Definition &def = modify_definition(op.args[1]);
|
|
|
|
|
- def._origin_id = op.args[2];
|
|
|
|
|
- def._type_id = get_definition(op.args[2])._type_id;
|
|
|
|
|
-
|
|
|
|
|
- // Copy the type since the storage class may have changed.
|
|
|
|
|
- op.args[0] = def._type_id;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpBitcast:
|
|
|
|
|
- nassertv(!deleted_ids.count(op.args[2]));
|
|
|
|
|
-
|
|
|
|
|
- if (deleted_access_chains.count(op.args[2])) {
|
|
|
|
|
- op.args[2] = deleted_access_chains[op.args[2]];
|
|
|
|
|
-
|
|
|
|
|
- Definition &def = modify_definition(op.args[1]);
|
|
|
|
|
- def._origin_id = op.args[2];
|
|
|
|
|
- }
|
|
|
|
|
- if (_defs[op.args[0]]._dtype != DT_type_pointer) {
|
|
|
|
|
- mark_used(op.args[1]);
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpSelect:
|
|
|
|
|
- mark_used(op.args[3]);
|
|
|
|
|
- mark_used(op.args[4]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpReturnValue:
|
|
|
|
|
- mark_used(op.args[0]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpCopyLogical:
|
|
|
|
|
- // Can't copy pointers using this instruction.
|
|
|
|
|
- nassertv(!deleted_ids.count(op.args[2]));
|
|
|
|
|
- nassertv(!deleted_access_chains.count(op.args[2]));
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpPtrEqual:
|
|
|
|
|
- case spv::OpPtrNotEqual:
|
|
|
|
|
- case spv::OpPtrDiff:
|
|
|
|
|
- mark_used(op.args[2]);
|
|
|
|
|
- mark_used(op.args[3]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- default:
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Insert names and decorations for the individual members.
|
|
|
|
|
- it = _instructions.begin_annotations();
|
|
|
|
|
-#ifndef NDEBUG
|
|
|
|
|
- for (uint32_t var_id : member_ids) {
|
|
|
|
|
- const std::string &member_name = _defs[var_id]._name;
|
|
|
|
|
- uint32_t nargs = 2 + member_name.size() / 4;
|
|
|
|
|
- uint32_t *args = (uint32_t *)alloca(nargs * 4);
|
|
|
|
|
- memset(args, 0, nargs * 4);
|
|
|
|
|
- args[0] = var_id;
|
|
|
|
|
- memcpy((char *)(args + 1), member_name.data(), member_name.size());
|
|
|
|
|
- it = _instructions.insert(it, spv::OpName, args, nargs);
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
-#endif
|
|
|
|
|
- for (uint32_t var_id : member_ids) {
|
|
|
|
|
- const Definition &var_def = get_definition(var_id);
|
|
|
|
|
- if (var_def.has_location()) {
|
|
|
|
|
- it = _instructions.insert(it,
|
|
|
|
|
- spv::OpDecorate, {var_id, spv::DecorationLocation, (uint32_t)var_def._location});
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Go over it again now that we know the deleted IDs, to remove any
|
|
|
|
|
- // decorations on them.
|
|
|
|
|
- if (deleted_ids.empty()) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- it = _instructions.begin();
|
|
|
|
|
- while (it != _instructions.end()) {
|
|
|
|
|
- Instruction op = *it;
|
|
|
|
|
-
|
|
|
|
|
- if ((op.opcode == spv::OpName ||
|
|
|
|
|
- op.opcode == spv::OpDecorate ||
|
|
|
|
|
- op.opcode == spv::OpDecorateId ||
|
|
|
|
|
- op.opcode == spv::OpDecorateString ||
|
|
|
|
|
- op.opcode == spv::OpMemberName ||
|
|
|
|
|
- op.opcode == spv::OpMemberDecorate ||
|
|
|
|
|
- op.opcode == spv::OpMemberDecorateString) &&
|
|
|
|
|
- op.nargs >= 2 && deleted_ids.count(op.args[0])) {
|
|
|
|
|
- _instructions.erase(it);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- it = _instructions.begin_functions();
|
|
|
|
|
-
|
|
|
|
|
- // Insert all the type pointers for the access chains.
|
|
|
|
|
- for (uint32_t id : insert_type_pointers) {
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypePointer,
|
|
|
|
|
- {id, spv::StorageClassUniformConstant, _defs[id]._type_id});
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Creates a new uniform block using the parameters specified by the given
|
|
|
|
|
- * locations and types. The opposite of flatten_struct, if you will.
|
|
|
|
|
- */
|
|
|
|
|
-uint32_t ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-make_block(const ShaderType::Struct *block_type, const pvector<int> &member_locations,
|
|
|
|
|
- spv::StorageClass storage_class, uint32_t binding, uint32_t set) {
|
|
|
|
|
- nassertr(block_type->get_num_members() == member_locations.size(), false);
|
|
|
|
|
-
|
|
|
|
|
- // Define block struct variable, which will implicitly define its type.
|
|
|
|
|
- uint32_t block_var_id = define_variable(block_type, storage_class);
|
|
|
|
|
- uint32_t block_type_id = _type_map[block_type];
|
|
|
|
|
- nassertr(block_type_id != 0, 0);
|
|
|
|
|
-
|
|
|
|
|
- // Collect type pointers that we have to create.
|
|
|
|
|
- pvector<uint32_t> insert_type_pointers;
|
|
|
|
|
-
|
|
|
|
|
- // Find the variables we should replace with members of this block by looking
|
|
|
|
|
- // at the locations. Collect a map of defined type pointers while we're at
|
|
|
|
|
- // it, so we don't unnecessarily duplicate them.
|
|
|
|
|
- pmap<uint32_t, uint32_t> member_indices;
|
|
|
|
|
- pmap<uint32_t, uint32_t> type_pointer_map;
|
|
|
|
|
-
|
|
|
|
|
- for (uint32_t id = 0; id < _defs.size(); ++id) {
|
|
|
|
|
- Definition &def = _defs[id];
|
|
|
|
|
- if (def._dtype == DT_type_pointer) {
|
|
|
|
|
- if (!def.has_builtin() && def._storage_class == storage_class) {
|
|
|
|
|
- // This is the storage class we need, store it in case we need it.
|
|
|
|
|
- type_pointer_map[def._type_id] = id;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- else if (def._dtype == DT_variable && def.has_location() &&
|
|
|
|
|
- def._storage_class == spv::StorageClassUniformConstant) {
|
|
|
|
|
-
|
|
|
|
|
- auto lit = std::find(member_locations.begin(), member_locations.end(), def._location);
|
|
|
|
|
- if (lit != member_locations.end()) {
|
|
|
|
|
- member_indices[id] = std::distance(member_locations.begin(), lit);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- uint32_t num_members = member_locations.size();
|
|
|
|
|
- uint32_t *allocation = (uint32_t *)alloca(num_members * sizeof(uint32_t) * 2);
|
|
|
|
|
- memset(allocation, 0, num_members * sizeof(uint32_t) * 2);
|
|
|
|
|
-
|
|
|
|
|
- uint32_t *member_type_ids = allocation;
|
|
|
|
|
- uint32_t *member_constant_ids = allocation + num_members;
|
|
|
|
|
-
|
|
|
|
|
- // Now add the decorations for the uniform block itself.
|
|
|
|
|
- InstructionIterator it = _instructions.end_annotations();
|
|
|
|
|
- it = _instructions.insert(it, spv::OpDecorate, {block_type_id, spv::DecorationBlock});
|
|
|
|
|
- ++it;
|
|
|
|
|
-
|
|
|
|
|
- if (storage_class != spv::StorageClassPushConstant) {
|
|
|
|
|
- it = _instructions.insert(it, spv::OpDecorate, {block_var_id, spv::DecorationBinding, binding});
|
|
|
|
|
- ++it;
|
|
|
|
|
- it = _instructions.insert(it, spv::OpDecorate, {block_var_id, spv::DecorationDescriptorSet, set});
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- it = _instructions.begin();
|
|
|
|
|
- while (it != _instructions.end()) {
|
|
|
|
|
- Instruction op = *it;
|
|
|
|
|
-
|
|
|
|
|
- switch (op.opcode) {
|
|
|
|
|
- case spv::OpName:
|
|
|
|
|
- // Translate an OpName to an OpMemberName for vars that become struct
|
|
|
|
|
- // members. We could just strip them, but this is useful for debugging.
|
|
|
|
|
- if (member_indices.count(op.args[0])) {
|
|
|
|
|
- uint32_t member_index = member_indices[op.args[0]];
|
|
|
|
|
-
|
|
|
|
|
- uint32_t nargs = op.nargs + 1;
|
|
|
|
|
- uint32_t *args = (uint32_t *)alloca(nargs * sizeof(uint32_t));
|
|
|
|
|
- args[0] = block_type_id;
|
|
|
|
|
- args[1] = member_index;
|
|
|
|
|
- memcpy(args + 2, op.args + 1, (op.nargs - 1) * sizeof(uint32_t));
|
|
|
|
|
-
|
|
|
|
|
- it = _instructions.insert(it, spv::OpMemberName, args, nargs);
|
|
|
|
|
- ++it;
|
|
|
|
|
- it = _instructions.erase(it);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpMemberName:
|
|
|
|
|
- case spv::OpDecorate:
|
|
|
|
|
- case spv::OpDecorateId:
|
|
|
|
|
- case spv::OpDecorateString:
|
|
|
|
|
- case spv::OpMemberDecorate:
|
|
|
|
|
- case spv::OpMemberDecorateString:
|
|
|
|
|
- // Remove other annotations on the members.
|
|
|
|
|
- if (op.nargs >= 1 && member_indices.count(op.args[0])) {
|
|
|
|
|
- it = _instructions.erase(it);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpConstant:
|
|
|
|
|
- // Store integer constants that are already defined in the file that may
|
|
|
|
|
- // be useful for defining our struct indices.
|
|
|
|
|
- if (op.args[2] < num_members &&
|
|
|
|
|
- (_defs[op.args[0]]._type == ShaderType::int_type ||
|
|
|
|
|
- _defs[op.args[0]]._type == ShaderType::uint_type)) {
|
|
|
|
|
- member_constant_ids[op.args[2]] = op.args[1];
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpVariable:
|
|
|
|
|
- if (member_indices.count(op.args[1])) {
|
|
|
|
|
- // Remove this variable. We'll replace it with an access chain later.
|
|
|
|
|
- uint32_t type_pointer_id = op.args[0];
|
|
|
|
|
- uint32_t member_id = op.args[1];
|
|
|
|
|
- uint32_t member_index = member_indices[member_id];
|
|
|
|
|
-
|
|
|
|
|
- if (_defs[type_pointer_id]._storage_class != storage_class) {
|
|
|
|
|
- // Get or create a type pointer with the correct storage class.
|
|
|
|
|
- uint32_t type_id = _defs[type_pointer_id]._type_id;
|
|
|
|
|
- auto tpi = type_pointer_map.find(type_id);
|
|
|
|
|
- if (tpi != type_pointer_map.end()) {
|
|
|
|
|
- type_pointer_id = tpi->second;
|
|
|
|
|
- } else {
|
|
|
|
|
- type_pointer_id = _instructions.allocate_id();
|
|
|
|
|
- type_pointer_map[type_id] = type_pointer_id;
|
|
|
|
|
- record_type_pointer(type_pointer_id, storage_class, type_id);
|
|
|
|
|
-
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypePointer,
|
|
|
|
|
- {type_pointer_id, (uint32_t)storage_class, type_id});
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- member_type_ids[member_index] = type_pointer_id;
|
|
|
|
|
-
|
|
|
|
|
- it = _instructions.erase(it);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpFunction:
|
|
|
|
|
- // Before we get to the function section, make sure that all the
|
|
|
|
|
- // remaining constants we need are defined.
|
|
|
|
|
- for (uint32_t i = 0; i < num_members; ++i) {
|
|
|
|
|
- uint32_t constant_id = member_constant_ids[i];
|
|
|
|
|
- if (constant_id == 0) {
|
|
|
|
|
- // Doesn't matter whether we pick uint or int, prefer whatever is
|
|
|
|
|
- // already defined.
|
|
|
|
|
- const ShaderType *type =
|
|
|
|
|
- _type_map.count(ShaderType::uint_type)
|
|
|
|
|
- ? ShaderType::uint_type
|
|
|
|
|
- : ShaderType::int_type;
|
|
|
|
|
- constant_id = r_define_constant(it, type, i);
|
|
|
|
|
- member_constant_ids[i] = constant_id;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpAccessChain:
|
|
|
|
|
- case spv::OpInBoundsAccessChain:
|
|
|
|
|
- if (member_indices.count(op.args[2])) {
|
|
|
|
|
- uint32_t member_index = member_indices[op.args[2]];
|
|
|
|
|
- uint32_t constant_id = member_constant_ids[member_index];
|
|
|
|
|
-
|
|
|
|
|
- // Get or create a type pointer with the correct storage class.
|
|
|
|
|
- uint32_t type_id = _defs[op.args[0]]._type_id;
|
|
|
|
|
- auto tpi = type_pointer_map.find(type_id);
|
|
|
|
|
- uint32_t type_pointer_id;
|
|
|
|
|
- if (tpi != type_pointer_map.end()) {
|
|
|
|
|
- type_pointer_id = tpi->second;
|
|
|
|
|
- } else {
|
|
|
|
|
- type_pointer_id = _instructions.allocate_id();
|
|
|
|
|
- type_pointer_map[type_id] = type_pointer_id;
|
|
|
|
|
- record_type_pointer(type_pointer_id, storage_class, type_id);
|
|
|
|
|
-
|
|
|
|
|
- // Can't create the type pointer immediately, since we're no longer
|
|
|
|
|
- // in the type declaration block. We'll add it at the end.
|
|
|
|
|
- insert_type_pointers.push_back(type_pointer_id);
|
|
|
|
|
- }
|
|
|
|
|
- op.args[0] = type_pointer_id;
|
|
|
|
|
-
|
|
|
|
|
- // Prepend our new block variable to the existing access chain.
|
|
|
|
|
- op.args[2] = block_var_id;
|
|
|
|
|
- it = _instructions.insert_arg(it, 3, constant_id);
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpImageTexelPointer:
|
|
|
|
|
- case spv::OpLoad:
|
|
|
|
|
- case spv::OpCopyObject:
|
|
|
|
|
- // Add access chains before all loads to access the right block member.
|
|
|
|
|
- if (member_indices.count(op.args[2])) {
|
|
|
|
|
- uint32_t member_index = member_indices[op.args[2]];
|
|
|
|
|
- uint32_t type_id = member_type_ids[member_index];
|
|
|
|
|
- uint32_t constant_id = member_constant_ids[member_index];
|
|
|
|
|
- uint32_t chain_id = _instructions.allocate_id();
|
|
|
|
|
-
|
|
|
|
|
- op.args[2] = chain_id;
|
|
|
|
|
- it = _instructions.insert(it, spv::OpInBoundsAccessChain,
|
|
|
|
|
- {type_id, chain_id, block_var_id, constant_id});
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpCopyMemory:
|
|
|
|
|
- case spv::OpCopyMemorySized:
|
|
|
|
|
- // Same as above, but these take the pointer in a different argument.
|
|
|
|
|
- if (member_indices.count(op.args[1])) {
|
|
|
|
|
- uint32_t member_index = member_indices[op.args[1]];
|
|
|
|
|
- uint32_t type_id = member_type_ids[member_index];
|
|
|
|
|
- uint32_t constant_id = member_constant_ids[member_index];
|
|
|
|
|
- uint32_t chain_id = _instructions.allocate_id();
|
|
|
|
|
-
|
|
|
|
|
- op.args[1] = chain_id;
|
|
|
|
|
- it = _instructions.insert(it, spv::OpInBoundsAccessChain,
|
|
|
|
|
- {type_id, chain_id, block_var_id, constant_id});
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- default:
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- it = _instructions.begin_functions();
|
|
|
|
|
-
|
|
|
|
|
- // Insert all the type pointers for the access chains.
|
|
|
|
|
- for (uint32_t id : insert_type_pointers) {
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypePointer,
|
|
|
|
|
- {id, (uint32_t)_defs[id]._storage_class, _defs[id]._type_id});
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return block_var_id;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Changes the type of the given variable. Does not check that the existing
|
|
|
|
|
- * usage of the variable in the shader is valid with the new type - it only
|
|
|
|
|
- * changes the types of loads and copies.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-set_variable_type(uint32_t variable_id, const ShaderType *type) {
|
|
|
|
|
- Definition &def = modify_definition(variable_id);
|
|
|
|
|
- nassertv(def._dtype == DT_variable);
|
|
|
|
|
-
|
|
|
|
|
- if (shader_cat.is_debug()) {
|
|
|
|
|
- shader_cat.debug()
|
|
|
|
|
- << "Changing type of variable " << variable_id << " (" << def._name
|
|
|
|
|
- << ") from " << *def._type << " to " << *type << "\n";
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- pset<uint32_t> pointer_ids, object_ids;
|
|
|
|
|
- pointer_ids.insert(variable_id);
|
|
|
|
|
-
|
|
|
|
|
- def._type = type;
|
|
|
|
|
-
|
|
|
|
|
- uint32_t type_pointer_id = 0;
|
|
|
|
|
- uint32_t type_id = 0;
|
|
|
|
|
-
|
|
|
|
|
- // We remove the variable and redefine it at the end (before the function
|
|
|
|
|
- // block), which is the easiest way to make really sure that the order of type
|
|
|
|
|
- // definitions is correct.
|
|
|
|
|
-
|
|
|
|
|
- bool inserted = false;
|
|
|
|
|
- InstructionIterator it = _instructions.end_annotations();
|
|
|
|
|
- while (it != _instructions.end()) {
|
|
|
|
|
- Instruction op = *it;
|
|
|
|
|
-
|
|
|
|
|
- switch (op.opcode) {
|
|
|
|
|
- case spv::OpVariable:
|
|
|
|
|
- // Erase the variable.
|
|
|
|
|
- if (op.args[1] == variable_id) {
|
|
|
|
|
- it = _instructions.erase(it);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpFunction:
|
|
|
|
|
- // Insert the new variable here, right before the function section.
|
|
|
|
|
- if (!inserted) {
|
|
|
|
|
- type_pointer_id = r_define_type_pointer(it, type, def._storage_class);
|
|
|
|
|
- type_id = _defs[type_pointer_id]._type_id;
|
|
|
|
|
-
|
|
|
|
|
- it = _instructions.insert(it, spv::OpVariable, {
|
|
|
|
|
- type_pointer_id,
|
|
|
|
|
- variable_id,
|
|
|
|
|
- (uint32_t)def._storage_class,
|
|
|
|
|
- });
|
|
|
|
|
- ++it;
|
|
|
|
|
- inserted = true;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpLoad:
|
|
|
|
|
- case spv::OpAtomicLoad:
|
|
|
|
|
- case spv::OpAtomicExchange:
|
|
|
|
|
- case spv::OpAtomicCompareExchange:
|
|
|
|
|
- case spv::OpAtomicCompareExchangeWeak:
|
|
|
|
|
- case spv::OpAtomicIIncrement:
|
|
|
|
|
- case spv::OpAtomicIDecrement:
|
|
|
|
|
- case spv::OpAtomicIAdd:
|
|
|
|
|
- case spv::OpAtomicISub:
|
|
|
|
|
- case spv::OpAtomicSMin:
|
|
|
|
|
- case spv::OpAtomicUMin:
|
|
|
|
|
- case spv::OpAtomicSMax:
|
|
|
|
|
- case spv::OpAtomicUMax:
|
|
|
|
|
- case spv::OpAtomicAnd:
|
|
|
|
|
- case spv::OpAtomicOr:
|
|
|
|
|
- case spv::OpAtomicXor:
|
|
|
|
|
- case spv::OpAtomicFMinEXT:
|
|
|
|
|
- case spv::OpAtomicFMaxEXT:
|
|
|
|
|
- case spv::OpAtomicFAddEXT:
|
|
|
|
|
- nassertd(inserted) break;
|
|
|
|
|
-
|
|
|
|
|
- // These loads turn a pointer into a dereferenced object.
|
|
|
|
|
- if (pointer_ids.count(op.args[2])) {
|
|
|
|
|
- op.args[0] = type_id;
|
|
|
|
|
- _defs[op.args[1]]._type = type;
|
|
|
|
|
- _defs[op.args[1]]._type_id = type_id;
|
|
|
|
|
- object_ids.insert(op.args[1]);
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpCopyObject:
|
|
|
|
|
- nassertd(inserted) break;
|
|
|
|
|
-
|
|
|
|
|
- // This clones a pointer or object verbatim, so keep following the chain.
|
|
|
|
|
- if (pointer_ids.count(op.args[2])) {
|
|
|
|
|
- op.args[0] = type_pointer_id;
|
|
|
|
|
- _defs[op.args[1]]._type = type;
|
|
|
|
|
- _defs[op.args[1]]._type_id = type_pointer_id;
|
|
|
|
|
- pointer_ids.insert(op.args[1]);
|
|
|
|
|
- }
|
|
|
|
|
- if (object_ids.count(op.args[2])) {
|
|
|
|
|
- op.args[0] = type_id;
|
|
|
|
|
- _defs[op.args[1]]._type = type;
|
|
|
|
|
- _defs[op.args[1]]._type_id = type_id;
|
|
|
|
|
- object_ids.insert(op.args[1]);
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpSelect:
|
|
|
|
|
- nassertd(inserted) break;
|
|
|
|
|
-
|
|
|
|
|
- // The result type for this op must be the same for both operands.
|
|
|
|
|
- nassertd(pointer_ids.count(op.args[3]) == pointer_ids.count(op.args[4]));
|
|
|
|
|
- nassertd(object_ids.count(op.args[3]) == object_ids.count(op.args[4]));
|
|
|
|
|
-
|
|
|
|
|
- if (pointer_ids.count(op.args[3])) {
|
|
|
|
|
- op.args[0] = type_pointer_id;
|
|
|
|
|
- _defs[op.args[1]]._type = type;
|
|
|
|
|
- _defs[op.args[1]]._type_id = type_pointer_id;
|
|
|
|
|
- pointer_ids.insert(op.args[1]);
|
|
|
|
|
- }
|
|
|
|
|
- if (object_ids.count(op.args[3])) {
|
|
|
|
|
- op.args[0] = type_id;
|
|
|
|
|
- _defs[op.args[1]]._type = type;
|
|
|
|
|
- _defs[op.args[1]]._type_id = type_id;
|
|
|
|
|
- object_ids.insert(op.args[1]);
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- default:
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
- nassertv(inserted);
|
|
|
|
|
-
|
|
|
|
|
- // Mark the type pointer and type as used if this variable was already marked
|
|
|
|
|
- // used.
|
|
|
|
|
- if (def.is_used()) {
|
|
|
|
|
- _defs[type_pointer_id]._flags |= DF_used;
|
|
|
|
|
- _defs[type_id]._flags |= DF_used;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Searches for an already-defined type pointer of the given storage class.
|
|
|
|
|
- * Returns its id, or 0 if it was not found.
|
|
|
|
|
- */
|
|
|
|
|
-uint32_t ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-find_type_pointer(const ShaderType *type, spv::StorageClass storage_class) {
|
|
|
|
|
- TypeMap::const_iterator tit = _type_map.find(type);
|
|
|
|
|
- if (tit == _type_map.end()) {
|
|
|
|
|
- return 0;
|
|
|
|
|
- }
|
|
|
|
|
- uint32_t type_id = tit->second;
|
|
|
|
|
-
|
|
|
|
|
- for (uint32_t id = 0; id < _defs.size(); ++id) {
|
|
|
|
|
- Definition &def = _defs[id];
|
|
|
|
|
- if (def._dtype == DT_type_pointer &&
|
|
|
|
|
- def._type_id == type_id &&
|
|
|
|
|
- def._storage_class == storage_class) {
|
|
|
|
|
- return id;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return 0;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Defines a new variable of the given type and storage class.
|
|
|
|
|
- */
|
|
|
|
|
-uint32_t ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-define_variable(const ShaderType *type, spv::StorageClass storage_class) {
|
|
|
|
|
- InstructionIterator it = _instructions.begin_functions();
|
|
|
|
|
- uint32_t id = r_define_variable(it, type, storage_class);
|
|
|
|
|
-
|
|
|
|
|
- // Depending on the storage class, we may need to make sure it is laid out.
|
|
|
|
|
- if (storage_class == spv::StorageClassStorageBuffer ||
|
|
|
|
|
- storage_class == spv::StorageClassPhysicalStorageBuffer ||
|
|
|
|
|
- storage_class == spv::StorageClassUniform ||
|
|
|
|
|
- storage_class == spv::StorageClassPushConstant) {
|
|
|
|
|
- it = _instructions.end_annotations();
|
|
|
|
|
- r_annotate_struct_layout(it, _type_map[type]);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return id;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Makes sure the given type pointer is defined.
|
|
|
|
|
- */
|
|
|
|
|
-uint32_t ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-define_type_pointer(const ShaderType *type, spv::StorageClass storage_class) {
|
|
|
|
|
- InstructionIterator it = _instructions.begin_functions();
|
|
|
|
|
- uint32_t type_id = r_define_type(it, type);
|
|
|
|
|
- uint32_t type_pointer_id = _instructions.allocate_id();
|
|
|
|
|
-
|
|
|
|
|
- record_type_pointer(type_pointer_id, storage_class, type_id);
|
|
|
|
|
- _instructions.insert(it, spv::OpTypePointer,
|
|
|
|
|
- {type_pointer_id, (uint32_t)storage_class, type_id});
|
|
|
|
|
-
|
|
|
|
|
- // Depending on the storage class, we may need to make sure it is laid out.
|
|
|
|
|
- if (storage_class == spv::StorageClassStorageBuffer ||
|
|
|
|
|
- storage_class == spv::StorageClassPhysicalStorageBuffer ||
|
|
|
|
|
- storage_class == spv::StorageClassUniform ||
|
|
|
|
|
- storage_class == spv::StorageClassPushConstant) {
|
|
|
|
|
- it = _instructions.end_annotations();
|
|
|
|
|
- r_annotate_struct_layout(it, type_id);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return type_pointer_id;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Makes sure the given type is defined.
|
|
|
|
|
- */
|
|
|
|
|
-uint32_t ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-define_type(const ShaderType *type) {
|
|
|
|
|
- InstructionIterator it = _instructions.begin_functions();
|
|
|
|
|
- return r_define_type(it, type);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Defines a new constant of the given type and value. If the type has not yet
|
|
|
|
|
- * been defined, defines it.
|
|
|
|
|
- */
|
|
|
|
|
-uint32_t ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-define_constant(const ShaderType *type, uint32_t constant) {
|
|
|
|
|
- InstructionIterator it = _instructions.begin_functions();
|
|
|
|
|
- return r_define_constant(it, type, constant);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Helper for define_variable that inserts a variable at the iterator, and then
|
|
|
|
|
- * advances the iterator.
|
|
|
|
|
- *
|
|
|
|
|
- * Note that unlike define_variable(), it does not ensure that any struct type
|
|
|
|
|
- * has been sufficiently laid out for this storage class.
|
|
|
|
|
- */
|
|
|
|
|
-uint32_t ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-r_define_variable(InstructionIterator &it, const ShaderType *type, spv::StorageClass storage_class) {
|
|
|
|
|
- uint32_t type_pointer_id = r_define_type_pointer(it, type, storage_class);
|
|
|
|
|
-
|
|
|
|
|
- uint32_t variable_id = _instructions.allocate_id();
|
|
|
|
|
- it = _instructions.insert(it, spv::OpVariable, {
|
|
|
|
|
- type_pointer_id,
|
|
|
|
|
- variable_id,
|
|
|
|
|
- (uint32_t)storage_class,
|
|
|
|
|
- });
|
|
|
|
|
- ++it;
|
|
|
|
|
-
|
|
|
|
|
- record_variable(variable_id, type_pointer_id, storage_class);
|
|
|
|
|
- return variable_id;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Helper for define_type_pointer that inserts a type pointer at the iterator
|
|
|
|
|
- * (though only if this type pointer doesn't exist yet), then advances the
|
|
|
|
|
- * iterator.
|
|
|
|
|
- *
|
|
|
|
|
- * Note that unlike define_type_pointer(), it does not ensure that any struct
|
|
|
|
|
- * type has been sufficiently laid out for this storage class.
|
|
|
|
|
- */
|
|
|
|
|
-uint32_t ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-r_define_type_pointer(InstructionIterator &it, const ShaderType *type, spv::StorageClass storage_class) {
|
|
|
|
|
- uint32_t type_pointer_id = find_type_pointer(type, storage_class);
|
|
|
|
|
- if (type_pointer_id != 0) {
|
|
|
|
|
- return type_pointer_id;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- uint32_t type_id = r_define_type(it, type);
|
|
|
|
|
- type_pointer_id = _instructions.allocate_id();
|
|
|
|
|
- record_type_pointer(type_pointer_id, storage_class, type_id);
|
|
|
|
|
-
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypePointer,
|
|
|
|
|
- {type_pointer_id, (uint32_t)storage_class, type_id});
|
|
|
|
|
- ++it;
|
|
|
|
|
-
|
|
|
|
|
- return type_pointer_id;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Helper for define_type. Inserts the given type (after any requisite
|
|
|
|
|
- * dependent types, as found through the given type map) at the given iterator,
|
|
|
|
|
- * and advances the iterator.
|
|
|
|
|
- */
|
|
|
|
|
-uint32_t ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-r_define_type(InstructionIterator &it, const ShaderType *type) {
|
|
|
|
|
- TypeMap::const_iterator tit = _type_map.find(type);
|
|
|
|
|
- if (tit != _type_map.end()) {
|
|
|
|
|
- return tit->second;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- uint32_t id = _instructions.allocate_id();
|
|
|
|
|
- record_type(id, type);
|
|
|
|
|
-
|
|
|
|
|
- if (const ShaderType::Scalar *scalar_type = type->as_scalar()) {
|
|
|
|
|
- switch (scalar_type->get_scalar_type()) {
|
|
|
|
|
- case ShaderType::ST_float:
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypeFloat, {id, 32});
|
|
|
|
|
- break;
|
|
|
|
|
- case ShaderType::ST_double:
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypeFloat, {id, 64});
|
|
|
|
|
- break;
|
|
|
|
|
- case ShaderType::ST_int:
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypeInt, {id, 32, 1});
|
|
|
|
|
- break;
|
|
|
|
|
- case ShaderType::ST_uint:
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypeInt, {id, 32, 0});
|
|
|
|
|
- break;
|
|
|
|
|
- case ShaderType::ST_bool:
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypeBool, {id});
|
|
|
|
|
- break;
|
|
|
|
|
- default:
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypeVoid, {id});
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- else if (const ShaderType::Vector *vector_type = type->as_vector()) {
|
|
|
|
|
- uint32_t component_type = r_define_type(it,
|
|
|
|
|
- ShaderType::register_type(ShaderType::Scalar(vector_type->get_scalar_type())));
|
|
|
|
|
-
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypeVector,
|
|
|
|
|
- {id, component_type, vector_type->get_num_components()});
|
|
|
|
|
- }
|
|
|
|
|
- else if (const ShaderType::Matrix *matrix_type = type->as_matrix()) {
|
|
|
|
|
- uint32_t row_type = r_define_type(it,
|
|
|
|
|
- ShaderType::register_type(ShaderType::Vector(matrix_type->get_scalar_type(), matrix_type->get_num_columns())));
|
|
|
|
|
-
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypeMatrix,
|
|
|
|
|
- {id, row_type, matrix_type->get_num_rows()});
|
|
|
|
|
- }
|
|
|
|
|
- else if (const ShaderType::Struct *struct_type = type->as_struct()) {
|
|
|
|
|
- size_t num_members = struct_type->get_num_members();
|
|
|
|
|
- uint32_t *args = (uint32_t *)alloca((1 + num_members) * sizeof(uint32_t));
|
|
|
|
|
- args[0] = id;
|
|
|
|
|
- uint32_t *member_types = args + 1;
|
|
|
|
|
-
|
|
|
|
|
- for (size_t i = 0; i < num_members; ++i) {
|
|
|
|
|
- const ShaderType::Struct::Member &member = struct_type->get_member(i);
|
|
|
|
|
-
|
|
|
|
|
- member_types[i] = r_define_type(it, member.type);
|
|
|
|
|
- _defs[id].modify_member(i)._type_id = member_types[i];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypeStruct, args, num_members + 1);
|
|
|
|
|
- }
|
|
|
|
|
- else if (const ShaderType::Array *array_type = type->as_array()) {
|
|
|
|
|
- uint32_t element_type = r_define_type(it, array_type->get_element_type());
|
|
|
|
|
-
|
|
|
|
|
- // Doesn't matter whether we pick uint or int, prefer whatever is
|
|
|
|
|
- // already defined.
|
|
|
|
|
- const ShaderType *constant_type =
|
|
|
|
|
- _type_map.count(ShaderType::uint_type)
|
|
|
|
|
- ? ShaderType::uint_type
|
|
|
|
|
- : ShaderType::int_type;
|
|
|
|
|
-
|
|
|
|
|
- auto size = array_type->get_num_elements();
|
|
|
|
|
- if (size != 0) {
|
|
|
|
|
- uint32_t constant_id = r_define_constant(it, constant_type, array_type->get_num_elements());
|
|
|
|
|
-
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypeArray,
|
|
|
|
|
- {id, element_type, constant_id});
|
|
|
|
|
- } else {
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypeRuntimeArray,
|
|
|
|
|
- {id, element_type});
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- else if (const ShaderType::Image *image_type = type->as_image()) {
|
|
|
|
|
- uint32_t args[9] = {
|
|
|
|
|
- id,
|
|
|
|
|
- r_define_type(it, ShaderType::register_type(ShaderType::Scalar(image_type->get_sampled_type()))),
|
|
|
|
|
- 0, // Dimensionality, see below
|
|
|
|
|
- 2, // Unspecified depthness
|
|
|
|
|
- 0, // Arrayness, see below
|
|
|
|
|
- 0, // Multisample not supported
|
|
|
|
|
- 0, // Sampled (unknown)
|
|
|
|
|
- spv::ImageFormatUnknown,
|
|
|
|
|
- 0, // Access qualifier
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- switch (image_type->get_texture_type()) {
|
|
|
|
|
- case Texture::TT_1d_texture:
|
|
|
|
|
- args[2] = spv::Dim1D;
|
|
|
|
|
- args[4] = 0;
|
|
|
|
|
- break;
|
|
|
|
|
- case Texture::TT_2d_texture:
|
|
|
|
|
- args[2] = spv::Dim2D;
|
|
|
|
|
- args[4] = 0;
|
|
|
|
|
- break;
|
|
|
|
|
- case Texture::TT_3d_texture:
|
|
|
|
|
- args[2] = spv::Dim3D;
|
|
|
|
|
- args[4] = 0;
|
|
|
|
|
- break;
|
|
|
|
|
- case Texture::TT_2d_texture_array:
|
|
|
|
|
- args[2] = spv::Dim2D;
|
|
|
|
|
- args[4] = 1;
|
|
|
|
|
- break;
|
|
|
|
|
- case Texture::TT_cube_map:
|
|
|
|
|
- args[2] = spv::DimCube;
|
|
|
|
|
- args[4] = 0;
|
|
|
|
|
- break;
|
|
|
|
|
- case Texture::TT_buffer_texture:
|
|
|
|
|
- args[2] = spv::DimBuffer;
|
|
|
|
|
- args[4] = 0;
|
|
|
|
|
- break;
|
|
|
|
|
- case Texture::TT_cube_map_array:
|
|
|
|
|
- args[2] = spv::DimCube;
|
|
|
|
|
- args[4] = 1;
|
|
|
|
|
- break;
|
|
|
|
|
- case Texture::TT_1d_texture_array:
|
|
|
|
|
- args[2] = spv::Dim1D;
|
|
|
|
|
- args[4] = 1;
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- uint32_t nargs = 8;
|
|
|
|
|
- switch (image_type->get_access()) {
|
|
|
|
|
- case ShaderType::Access::none:
|
|
|
|
|
- case ShaderType::Access::read_only:
|
|
|
|
|
- args[8] = spv::AccessQualifierReadOnly;
|
|
|
|
|
- ++nargs;
|
|
|
|
|
- break;
|
|
|
|
|
- case ShaderType::Access::write_only:
|
|
|
|
|
- args[8] = spv::AccessQualifierWriteOnly;
|
|
|
|
|
- ++nargs;
|
|
|
|
|
- break;
|
|
|
|
|
- case ShaderType::Access::read_write:
|
|
|
|
|
- args[8] = spv::AccessQualifierReadWrite;
|
|
|
|
|
- ++nargs;
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypeImage, args, nargs);
|
|
|
|
|
- }
|
|
|
|
|
- else if (type->as_sampler() != nullptr) {
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypeSampler, {id});
|
|
|
|
|
- }
|
|
|
|
|
- else if (const ShaderType::SampledImage *sampled_image_type = type->as_sampled_image()) {
|
|
|
|
|
- // We insert the image type here as well, because there are some specifics
|
|
|
|
|
- // about the image definition that we need to get right.
|
|
|
|
|
- uint32_t image_id = _instructions.allocate_id();
|
|
|
|
|
- uint32_t args[8] = {
|
|
|
|
|
- image_id,
|
|
|
|
|
- r_define_type(it, ShaderType::register_type(ShaderType::Scalar(sampled_image_type->get_sampled_type()))),
|
|
|
|
|
- 0, // Dimensionality, see below
|
|
|
|
|
- sampled_image_type->is_shadow() ? (uint32_t)1 : (uint32_t)0, // Depthness
|
|
|
|
|
- 0, // Arrayness, see below
|
|
|
|
|
- 0, // Multisample not supported
|
|
|
|
|
- 1, // Sampled
|
|
|
|
|
- spv::ImageFormatUnknown,
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- switch (sampled_image_type->get_texture_type()) {
|
|
|
|
|
- case Texture::TT_1d_texture:
|
|
|
|
|
- args[2] = spv::Dim1D;
|
|
|
|
|
- args[4] = 0;
|
|
|
|
|
- break;
|
|
|
|
|
- case Texture::TT_2d_texture:
|
|
|
|
|
- args[2] = spv::Dim2D;
|
|
|
|
|
- args[4] = 0;
|
|
|
|
|
- break;
|
|
|
|
|
- case Texture::TT_3d_texture:
|
|
|
|
|
- args[2] = spv::Dim3D;
|
|
|
|
|
- args[4] = 0;
|
|
|
|
|
- break;
|
|
|
|
|
- case Texture::TT_2d_texture_array:
|
|
|
|
|
- args[2] = spv::Dim2D;
|
|
|
|
|
- args[4] = 1;
|
|
|
|
|
- break;
|
|
|
|
|
- case Texture::TT_cube_map:
|
|
|
|
|
- args[2] = spv::DimCube;
|
|
|
|
|
- args[4] = 0;
|
|
|
|
|
- break;
|
|
|
|
|
- case Texture::TT_buffer_texture:
|
|
|
|
|
- args[2] = spv::DimBuffer;
|
|
|
|
|
- args[4] = 0;
|
|
|
|
|
- break;
|
|
|
|
|
- case Texture::TT_cube_map_array:
|
|
|
|
|
- args[2] = spv::DimCube;
|
|
|
|
|
- args[4] = 1;
|
|
|
|
|
- break;
|
|
|
|
|
- case Texture::TT_1d_texture_array:
|
|
|
|
|
- args[2] = spv::Dim1D;
|
|
|
|
|
- args[4] = 1;
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypeImage, args, 8);
|
|
|
|
|
- ++it;
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypeSampledImage, {id, image_id});
|
|
|
|
|
- }
|
|
|
|
|
- else {
|
|
|
|
|
- it = _instructions.insert(it, spv::OpTypeVoid, {id});
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- ++it;
|
|
|
|
|
- return id;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Helper for define_constant that inserts a variable at the iterator, and then
|
|
|
|
|
- * advances the iterator.
|
|
|
|
|
- */
|
|
|
|
|
-uint32_t ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-r_define_constant(InstructionIterator &it, const ShaderType *type, uint32_t constant) {
|
|
|
|
|
- uint32_t type_id = r_define_type(it, type);
|
|
|
|
|
-
|
|
|
|
|
- uint32_t constant_id = _instructions.allocate_id();
|
|
|
|
|
- it = _instructions.insert(it, spv::OpConstant, {type_id, constant_id, constant});
|
|
|
|
|
- ++it;
|
|
|
|
|
-
|
|
|
|
|
- record_constant(constant_id, type_id, &constant, 1);
|
|
|
|
|
- return constant_id;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Makes sure that the given type has all its structure members correctly laid
|
|
|
|
|
- * out using offsets and strides.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-r_annotate_struct_layout(InstructionIterator &it, uint32_t type_id) {
|
|
|
|
|
- const ShaderType *type = _defs[type_id]._type;
|
|
|
|
|
- nassertv(type != nullptr);
|
|
|
|
|
-
|
|
|
|
|
- const ShaderType::Struct *struct_type = type->as_struct();
|
|
|
|
|
- if (struct_type == nullptr) {
|
|
|
|
|
- // If this is an array of structs, recurse.
|
|
|
|
|
- if (const ShaderType::Array *array_type = type->as_array()) {
|
|
|
|
|
- // Also make sure there's an ArrayStride decoration for this array.
|
|
|
|
|
- Definition &array_def = _defs[type_id];
|
|
|
|
|
-
|
|
|
|
|
- if (array_def._array_stride == 0) {
|
|
|
|
|
- array_def._array_stride = array_type->get_stride_bytes();
|
|
|
|
|
- it = _instructions.insert(it, spv::OpDecorate,
|
|
|
|
|
- {type_id, spv::DecorationArrayStride, array_def._array_stride});
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- uint32_t element_type_id = _type_map[array_type->get_element_type()];
|
|
|
|
|
- r_annotate_struct_layout(it, element_type_id);
|
|
|
|
|
- }
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- uint32_t num_members = struct_type->get_num_members();
|
|
|
|
|
-
|
|
|
|
|
- for (uint32_t i = 0; i < num_members; ++i) {
|
|
|
|
|
- const ShaderType::Struct::Member &member = struct_type->get_member(i);
|
|
|
|
|
-
|
|
|
|
|
- MemberDefinition &member_def = _defs[type_id].modify_member(i);
|
|
|
|
|
- if (member_def._offset < 0) {
|
|
|
|
|
- member_def._offset = member.offset;
|
|
|
|
|
-
|
|
|
|
|
- it = _instructions.insert(it, spv::OpMemberDecorate,
|
|
|
|
|
- {type_id, i, spv::DecorationOffset, member.offset});
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Unwrap array to see if there's a matrix here.
|
|
|
|
|
- const ShaderType *base_type = member.type;
|
|
|
|
|
- while (const ShaderType::Array *array_type = base_type->as_array()) {
|
|
|
|
|
- base_type = array_type->get_element_type();
|
|
|
|
|
-
|
|
|
|
|
- // Also make sure there's an ArrayStride decoration for this array.
|
|
|
|
|
- uint32_t array_type_id = _type_map[array_type];
|
|
|
|
|
- Definition &array_def = _defs[array_type_id];
|
|
|
|
|
-
|
|
|
|
|
- if (array_def._array_stride == 0) {
|
|
|
|
|
- array_def._array_stride = array_type->get_stride_bytes();
|
|
|
|
|
- it = _instructions.insert(it, spv::OpDecorate,
|
|
|
|
|
- {array_type_id, spv::DecorationArrayStride, array_def._array_stride});
|
|
|
|
|
- ++it;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (const ShaderType::Matrix *matrix_type = base_type->as_matrix()) {
|
|
|
|
|
- // Matrix types need to be explicitly laid out.
|
|
|
|
|
- it = _instructions.insert(it, spv::OpMemberDecorate,
|
|
|
|
|
- {type_id, i, spv::DecorationMatrixStride, matrix_type->get_num_columns() * 4});
|
|
|
|
|
- ++it;
|
|
|
|
|
- it = _instructions.insert(it, spv::OpMemberDecorate,
|
|
|
|
|
- {type_id, i, spv::DecorationColMajor});
|
|
|
|
|
- ++it;
|
|
|
|
|
- } else {
|
|
|
|
|
- r_annotate_struct_layout(it, member_def._type_id);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Parses the instruction with the given SPIR-V opcode and arguments. Any
|
|
|
|
|
- * encountered definitions are recorded in the definitions vector.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-parse_instruction(const Instruction &op, uint32_t ¤t_function_id) {
|
|
|
|
|
- switch (op.opcode) {
|
|
|
|
|
- case spv::OpExtInstImport:
|
|
|
|
|
- record_ext_inst_import(op.args[0], (const char*)&op.args[1]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpExtInst:
|
|
|
|
|
- {
|
|
|
|
|
- const Definition &def = get_definition(op.args[2]);
|
|
|
|
|
- nassertv(def._dtype == DT_ext_inst);
|
|
|
|
|
- if (def._name == "GLSL.std.450") {
|
|
|
|
|
- // These standard functions take pointers as arguments.
|
|
|
|
|
- switch (op.args[3]) {
|
|
|
|
|
- case GLSLstd450Modf:
|
|
|
|
|
- case GLSLstd450Frexp:
|
|
|
|
|
- mark_used(op.args[5]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case GLSLstd450InterpolateAtCentroid:
|
|
|
|
|
- mark_used(op.args[4]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case GLSLstd450InterpolateAtSample:
|
|
|
|
|
- case GLSLstd450InterpolateAtOffset:
|
|
|
|
|
- mark_used(op.args[4]);
|
|
|
|
|
- mark_used(op.args[5]);
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpName:
|
|
|
|
|
- _defs[op.args[0]]._name.assign((const char *)&op.args[1]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpMemberName:
|
|
|
|
|
- _defs[op.args[0]].modify_member(op.args[1])._name.assign((const char *)&op.args[2]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpTypeVoid:
|
|
|
|
|
- record_type(op.args[0], nullptr);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpTypeBool:
|
|
|
|
|
- record_type(op.args[0], ShaderType::bool_type);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpTypeInt:
|
|
|
|
|
- {
|
|
|
|
|
- if (op.args[2]) {
|
|
|
|
|
- record_type(op.args[0], ShaderType::int_type);
|
|
|
|
|
- } else {
|
|
|
|
|
- record_type(op.args[0], ShaderType::uint_type);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpTypeFloat:
|
|
|
|
|
- {
|
|
|
|
|
- if (op.nargs >= 2 && op.args[1] >= 64) {
|
|
|
|
|
- record_type(op.args[0], ShaderType::double_type);
|
|
|
|
|
- } else {
|
|
|
|
|
- record_type(op.args[0], ShaderType::float_type);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpTypeVector:
|
|
|
|
|
- {
|
|
|
|
|
- const ShaderType::Scalar *element_type;
|
|
|
|
|
- DCAST_INTO_V(element_type, _defs[op.args[1]]._type);
|
|
|
|
|
- uint32_t component_count = op.args[2];
|
|
|
|
|
- record_type(op.args[0], ShaderType::register_type(
|
|
|
|
|
- ShaderType::Vector(element_type->get_scalar_type(), component_count)));
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpTypeMatrix:
|
|
|
|
|
- {
|
|
|
|
|
- const ShaderType::Vector *column_type;
|
|
|
|
|
- DCAST_INTO_V(column_type, _defs[op.args[1]]._type);
|
|
|
|
|
- uint32_t num_rows = op.args[2];
|
|
|
|
|
- record_type(op.args[0], ShaderType::register_type(
|
|
|
|
|
- ShaderType::Matrix(column_type->get_scalar_type(), num_rows, column_type->get_num_components())));
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpTypePointer:
|
|
|
|
|
- if (current_function_id != 0) {
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "OpTypePointer" << " may not occur within a function!\n";
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- record_type_pointer(op.args[0], (spv::StorageClass)op.args[1], op.args[2]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpTypeImage:
|
|
|
|
|
- {
|
|
|
|
|
- const ShaderType::Scalar *sampled_type;
|
|
|
|
|
- DCAST_INTO_V(sampled_type, _defs[op.args[1]]._type);
|
|
|
|
|
-
|
|
|
|
|
- Texture::TextureType texture_type;
|
|
|
|
|
- switch ((spv::Dim)op.args[2]) {
|
|
|
|
|
- case spv::Dim1D:
|
|
|
|
|
- if (op.args[4]) {
|
|
|
|
|
- texture_type = Texture::TT_1d_texture_array;
|
|
|
|
|
- } else {
|
|
|
|
|
- texture_type = Texture::TT_1d_texture;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::Dim2D:
|
|
|
|
|
- if (op.args[4]) {
|
|
|
|
|
- texture_type = Texture::TT_2d_texture_array;
|
|
|
|
|
- } else {
|
|
|
|
|
- texture_type = Texture::TT_2d_texture;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::Dim3D:
|
|
|
|
|
- texture_type = Texture::TT_3d_texture;
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::DimCube:
|
|
|
|
|
- if (op.args[4]) {
|
|
|
|
|
- texture_type = Texture::TT_cube_map_array;
|
|
|
|
|
- } else {
|
|
|
|
|
- texture_type = Texture::TT_cube_map;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::DimRect:
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "imageRect shader inputs are not supported.\n";
|
|
|
|
|
- return;
|
|
|
|
|
-
|
|
|
|
|
- case spv::DimBuffer:
|
|
|
|
|
- texture_type = Texture::TT_buffer_texture;
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::DimSubpassData:
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "subpassInput shader inputs are not supported.\n";
|
|
|
|
|
- return;
|
|
|
|
|
-
|
|
|
|
|
- default:
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "Unknown image dimensionality in OpTypeImage instruction.\n";
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- ShaderType::Access access = ShaderType::Access::read_write;
|
|
|
|
|
- if (op.nargs > 8) {
|
|
|
|
|
- switch ((spv::AccessQualifier)op.args[8]) {
|
|
|
|
|
- case spv::AccessQualifierReadOnly:
|
|
|
|
|
- access = ShaderType::Access::read_only;
|
|
|
|
|
- break;
|
|
|
|
|
- case spv::AccessQualifierWriteOnly:
|
|
|
|
|
- access = ShaderType::Access::write_only;
|
|
|
|
|
- break;
|
|
|
|
|
- case spv::AccessQualifierReadWrite:
|
|
|
|
|
- access = ShaderType::Access::read_write;
|
|
|
|
|
- break;
|
|
|
|
|
- default:
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "Invalid access qualifier in OpTypeImage instruction.\n";
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- if (_defs[op.args[0]]._flags & DF_non_writable) {
|
|
|
|
|
- access = (access & ShaderType::Access::read_only);
|
|
|
|
|
- }
|
|
|
|
|
- if (_defs[op.args[0]]._flags & DF_non_readable) {
|
|
|
|
|
- access = (access & ShaderType::Access::write_only);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- record_type(op.args[0], ShaderType::register_type(
|
|
|
|
|
- ShaderType::Image(texture_type, sampled_type->get_scalar_type(), access)));
|
|
|
|
|
-
|
|
|
|
|
- // We don't record the "depth" flag on the image type (because no shader
|
|
|
|
|
- // language actually does that), so we have to store it somewhere else.
|
|
|
|
|
- if (op.args[3] == 1) {
|
|
|
|
|
- _defs[op.args[0]]._flags |= DF_depth_image;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpTypeSampler:
|
|
|
|
|
- // A sampler that's not bound to a particular image.
|
|
|
|
|
- record_type(op.args[0], ShaderType::sampler_type);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpTypeSampledImage:
|
|
|
|
|
- if (const ShaderType::Image *image = _defs[op.args[1]]._type->as_image()) {
|
|
|
|
|
- bool shadow = (_defs[op.args[1]]._flags & DF_depth_image) != 0;
|
|
|
|
|
- record_type(op.args[0], ShaderType::register_type(
|
|
|
|
|
- ShaderType::SampledImage(image->get_texture_type(), image->get_sampled_type(), shadow)));
|
|
|
|
|
- } else {
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "OpTypeSampledImage must refer to an image type!\n";
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpTypeArray:
|
|
|
|
|
- if (_defs[op.args[1]]._type != nullptr) {
|
|
|
|
|
- record_type(op.args[0], ShaderType::register_type(
|
|
|
|
|
- ShaderType::Array(_defs[op.args[1]]._type, _defs[op.args[2]]._constant)));
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpTypeRuntimeArray:
|
|
|
|
|
- if (_defs[op.args[1]]._type != nullptr) {
|
|
|
|
|
- record_type(op.args[0], ShaderType::register_type(
|
|
|
|
|
- ShaderType::Array(_defs[op.args[1]]._type, 0)));
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpTypeStruct:
|
|
|
|
|
- {
|
|
|
|
|
- Definition &struct_def = _defs[op.args[0]];
|
|
|
|
|
- int access_flags = DF_non_writable | DF_non_readable;
|
|
|
|
|
- ShaderType::Struct type;
|
|
|
|
|
- for (size_t i = 0; i < op.nargs - 1; ++i) {
|
|
|
|
|
- uint32_t member_type_id = op.args[i + 1];
|
|
|
|
|
- if (member_type_id >= _defs.size() || _defs[member_type_id]._dtype != DT_type) {
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "Struct type with id " << op.args[0]
|
|
|
|
|
- << " contains invalid member type " << member_type_id << "\n";
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- MemberDefinition &member_def = struct_def.modify_member(i);
|
|
|
|
|
- member_def._type_id = member_type_id;
|
|
|
|
|
- if (member_def._builtin != spv::BuiltInMax) {
|
|
|
|
|
- // Ignore built-in member.
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const ShaderType *member_type = _defs[member_type_id]._type;
|
|
|
|
|
- if (member_def._flags & (DF_non_writable | DF_non_readable)) {
|
|
|
|
|
- // If an image member has the readonly/writeonly qualifiers,
|
|
|
|
|
- // then we'll inject those back into the type.
|
|
|
|
|
- if (const ShaderType::Image *image = member_type->as_image()) {
|
|
|
|
|
- ShaderType::Access access = image->get_access();
|
|
|
|
|
- if (member_def._flags & DF_non_writable) {
|
|
|
|
|
- access = (access & ShaderType::Access::read_only);
|
|
|
|
|
- }
|
|
|
|
|
- if (member_def._flags & DF_non_readable) {
|
|
|
|
|
- access = (access & ShaderType::Access::write_only);
|
|
|
|
|
- }
|
|
|
|
|
- if (access != image->get_access()) {
|
|
|
|
|
- member_type = ShaderType::register_type(ShaderType::Image(
|
|
|
|
|
- image->get_texture_type(),
|
|
|
|
|
- image->get_sampled_type(),
|
|
|
|
|
- access));
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- if (member_def._offset >= 0) {
|
|
|
|
|
- type.add_member(member_type, member_def._name, (uint32_t)member_def._offset);
|
|
|
|
|
- } else {
|
|
|
|
|
- type.add_member(member_type, member_def._name);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // If any member is writable, the struct shan't be marked readonly.
|
|
|
|
|
- access_flags &= member_def._flags;
|
|
|
|
|
- }
|
|
|
|
|
- record_type(op.args[0], ShaderType::register_type(std::move(type)));
|
|
|
|
|
-
|
|
|
|
|
- // If all struct members are flagged readonly/writeonly, we tag the type
|
|
|
|
|
- // so as well, since glslang doesn't decorate an SSBO in its entirety as
|
|
|
|
|
- // readonly/writeonly properly (it applies it to all members instead)
|
|
|
|
|
- _defs[op.args[0]]._flags |= access_flags;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpConstant:
|
|
|
|
|
- if (current_function_id != 0) {
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "OpConstant" << " may not occur within a function!\n";
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- record_constant(op.args[1], op.args[0], op.args + 2, op.nargs - 2);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpConstantNull:
|
|
|
|
|
- if (current_function_id != 0) {
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "OpConstantNull" << " may not occur within a function!\n";
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- record_constant(op.args[1], op.args[0], nullptr, 0);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpConstantComposite:
|
|
|
|
|
- case spv::OpSpecConstantComposite:
|
|
|
|
|
- modify_definition(op.args[1])._flags |= DF_constant_expression;
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpSpecConstantTrue:
|
|
|
|
|
- case spv::OpSpecConstantFalse:
|
|
|
|
|
- case spv::OpSpecConstant:
|
|
|
|
|
- record_spec_constant(op.args[1], op.args[0]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpFunction:
|
|
|
|
|
- if (current_function_id != 0) {
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "OpFunction may not occur within another function!\n";
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- {
|
|
|
|
|
- const Definition &func_def = modify_definition(op.args[1]);
|
|
|
|
|
- if (func_def._dtype == DT_function && func_def._type_id != op.args[0]) {
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "OpFunctionCall has mismatched return type ("
|
|
|
|
|
- << op.args[0] << " != " << func_def._type_id << ")\n";
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- current_function_id = op.args[1];
|
|
|
|
|
- record_function(op.args[1], op.args[0]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpFunctionParameter:
|
|
|
|
|
- if (current_function_id == 0) {
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "OpFunctionParameter" << " may only occur within a function!\n";
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- record_function_parameter(op.args[1], op.args[0], current_function_id);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpFunctionEnd:
|
|
|
|
|
- if (current_function_id == 0) {
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "OpFunctionEnd" << " may only occur within a function!\n";
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- current_function_id = 0;
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpFunctionCall:
|
|
|
|
|
- if (current_function_id == 0) {
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "OpFunctionCall" << " may only occur within a function!\n";
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- {
|
|
|
|
|
- Definition &func_def = modify_definition(op.args[2]);
|
|
|
|
|
-
|
|
|
|
|
- // Mark all arguments as used. In the future we could be smart enough to
|
|
|
|
|
- // only mark the arguments used if the relevant parameters are used with
|
|
|
|
|
- // the function itself.
|
|
|
|
|
- for (size_t i = 3; i < op.nargs; ++i) {
|
|
|
|
|
- mark_used(op.args[i]);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Error checking. Note that it's valid for the function to not yet have
|
|
|
|
|
- // been defined.
|
|
|
|
|
- if (func_def._dtype == DT_function) {
|
|
|
|
|
- if (func_def._type_id != 0 && func_def._type_id != op.args[0]) {
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "OpFunctionCall has mismatched return type ("
|
|
|
|
|
- << func_def._type_id << " != " << op.args[0] << ")\n";
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- else if (func_def._dtype != DT_none) {
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "OpFunctionCall tries to call non-function definition "
|
|
|
|
|
- << op.args[2] << "\n";
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Mark the function as used (even if its return value is unused - the
|
|
|
|
|
- // function may have side effects). Note that it's legal for the function
|
|
|
|
|
- // to not yet have been declared.
|
|
|
|
|
- func_def._dtype = DT_function;
|
|
|
|
|
- func_def._flags |= DF_used;
|
|
|
|
|
- func_def._type_id = op.args[0];
|
|
|
|
|
- record_temporary(op.args[1], op.args[0], op.args[2], current_function_id);
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpVariable:
|
|
|
|
|
- record_variable(op.args[1], op.args[0], (spv::StorageClass)op.args[2], current_function_id);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpImageTexelPointer:
|
|
|
|
|
- record_temporary(op.args[1], op.args[0], op.args[2], current_function_id);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpLoad:
|
|
|
|
|
- case spv::OpAtomicLoad:
|
|
|
|
|
- case spv::OpAtomicExchange:
|
|
|
|
|
- case spv::OpAtomicCompareExchange:
|
|
|
|
|
- case spv::OpAtomicCompareExchangeWeak:
|
|
|
|
|
- case spv::OpAtomicIIncrement:
|
|
|
|
|
- case spv::OpAtomicIDecrement:
|
|
|
|
|
- case spv::OpAtomicIAdd:
|
|
|
|
|
- case spv::OpAtomicISub:
|
|
|
|
|
- case spv::OpAtomicSMin:
|
|
|
|
|
- case spv::OpAtomicUMin:
|
|
|
|
|
- case spv::OpAtomicSMax:
|
|
|
|
|
- case spv::OpAtomicUMax:
|
|
|
|
|
- case spv::OpAtomicAnd:
|
|
|
|
|
- case spv::OpAtomicOr:
|
|
|
|
|
- case spv::OpAtomicXor:
|
|
|
|
|
- case spv::OpAtomicFlagTestAndSet:
|
|
|
|
|
- case spv::OpAtomicFMinEXT:
|
|
|
|
|
- case spv::OpAtomicFMaxEXT:
|
|
|
|
|
- case spv::OpAtomicFAddEXT:
|
|
|
|
|
- record_temporary(op.args[1], op.args[0], op.args[2], current_function_id);
|
|
|
|
|
-
|
|
|
|
|
- // A load from the pointer is enough for us to consider it "used", for now.
|
|
|
|
|
- mark_used(op.args[1]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpStore:
|
|
|
|
|
- case spv::OpAtomicStore:
|
|
|
|
|
- case spv::OpAtomicFlagClear:
|
|
|
|
|
- // An atomic write creates no result ID, but we do consider the var "used".
|
|
|
|
|
- mark_used(op.args[0]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpCopyMemory:
|
|
|
|
|
- case spv::OpCopyMemorySized:
|
|
|
|
|
- mark_used(op.args[0]);
|
|
|
|
|
- mark_used(op.args[1]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpAccessChain:
|
|
|
|
|
- case spv::OpInBoundsAccessChain:
|
|
|
|
|
- case spv::OpPtrAccessChain:
|
|
|
|
|
- case spv::OpInBoundsPtrAccessChain:
|
|
|
|
|
- // Record the access chain or pointer copy, so that as soon as something is
|
|
|
|
|
- // loaded through them we can transitively mark everything as "used".
|
|
|
|
|
- record_temporary(op.args[1], op.args[0], op.args[2], current_function_id);
|
|
|
|
|
-
|
|
|
|
|
- // If one of the indices (including the base element for OpPtrAccessChain)
|
|
|
|
|
- // isn't a constant expression, we mark the variable as dynamically-indexed.
|
|
|
|
|
- for (size_t i = 3; i < op.nargs; ++i) {
|
|
|
|
|
- if ((_defs[op.args[i]]._flags & DF_constant_expression) == 0) {
|
|
|
|
|
- const Definition &def = get_definition(op.args[1]);
|
|
|
|
|
- nassertv(def._origin_id != 0);
|
|
|
|
|
- _defs[def._origin_id]._flags |= DF_dynamically_indexed;
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpArrayLength:
|
|
|
|
|
- case spv::OpConvertPtrToU:
|
|
|
|
|
- mark_used(op.args[2]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpDecorate:
|
|
|
|
|
- switch ((spv::Decoration)op.args[1]) {
|
|
|
|
|
- case spv::DecorationBufferBlock:
|
|
|
|
|
- _defs[op.args[0]]._flags |= DF_buffer_block;
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::DecorationBuiltIn:
|
|
|
|
|
- _defs[op.args[0]]._builtin = (spv::BuiltIn)op.args[2];
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::DecorationNonWritable:
|
|
|
|
|
- _defs[op.args[0]]._flags |= DF_non_writable;
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::DecorationNonReadable:
|
|
|
|
|
- _defs[op.args[0]]._flags |= DF_non_readable;
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::DecorationLocation:
|
|
|
|
|
- _defs[op.args[0]]._location = op.args[2];
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::DecorationArrayStride:
|
|
|
|
|
- _defs[op.args[0]]._array_stride = op.args[2];
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::DecorationSpecId:
|
|
|
|
|
- _defs[op.args[0]]._spec_id = op.args[2];
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- default:
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpMemberDecorate:
|
|
|
|
|
- switch ((spv::Decoration)op.args[2]) {
|
|
|
|
|
- case spv::DecorationBuiltIn:
|
|
|
|
|
- _defs[op.args[0]].modify_member(op.args[1])._builtin = (spv::BuiltIn)op.args[3];
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::DecorationNonWritable:
|
|
|
|
|
- _defs[op.args[0]].modify_member(op.args[1])._flags |= DF_non_writable;
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::DecorationNonReadable:
|
|
|
|
|
- _defs[op.args[0]].modify_member(op.args[1])._flags |= DF_non_readable;
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::DecorationLocation:
|
|
|
|
|
- _defs[op.args[0]].modify_member(op.args[1])._location = op.args[3];
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::DecorationBinding:
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "Invalid " << "binding" << " decoration on struct member\n";
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::DecorationDescriptorSet:
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "Invalid " << "set" << " decoration on struct member\n";
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::DecorationOffset:
|
|
|
|
|
- _defs[op.args[0]].modify_member(op.args[1])._offset = op.args[3];
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- default:
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpCompositeConstruct:
|
|
|
|
|
- //XXX Not sure that we even need this, since it's probably not possible to
|
|
|
|
|
- // construct a composite from pointers?
|
|
|
|
|
- for (size_t i = 2; i < op.nargs; ++i) {
|
|
|
|
|
- mark_used(op.args[i]);
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpCopyObject:
|
|
|
|
|
- record_temporary(op.args[1], op.args[0], op.args[2], current_function_id);
|
|
|
|
|
- // fall through
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpCompositeExtract:
|
|
|
|
|
- // Composite types are used for some arithmetic ops.
|
|
|
|
|
- if (_defs[op.args[2]]._flags & DF_constant_expression) {
|
|
|
|
|
- _defs[op.args[1]]._flags |= DF_constant_expression;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpImageSampleImplicitLod:
|
|
|
|
|
- case spv::OpImageSampleExplicitLod:
|
|
|
|
|
- case spv::OpImageSampleProjImplicitLod:
|
|
|
|
|
- case spv::OpImageSampleProjExplicitLod:
|
|
|
|
|
- case spv::OpImageFetch:
|
|
|
|
|
- case spv::OpImageGather:
|
|
|
|
|
- case spv::OpImageSparseSampleImplicitLod:
|
|
|
|
|
- case spv::OpImageSparseSampleExplicitLod:
|
|
|
|
|
- case spv::OpImageSparseSampleProjImplicitLod:
|
|
|
|
|
- case spv::OpImageSparseSampleProjExplicitLod:
|
|
|
|
|
- case spv::OpImageSparseFetch:
|
|
|
|
|
- case spv::OpImageSparseGather:
|
|
|
|
|
- // Indicate that this variable was sampled with a non-dref sampler.
|
|
|
|
|
- {
|
|
|
|
|
- uint32_t var_id = _defs[op.args[2]]._origin_id;
|
|
|
|
|
- if (var_id != 0) {
|
|
|
|
|
- _defs[var_id]._flags |= DF_non_dref_sampled;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpImageSampleDrefImplicitLod:
|
|
|
|
|
- case spv::OpImageSampleDrefExplicitLod:
|
|
|
|
|
- case spv::OpImageSampleProjDrefImplicitLod:
|
|
|
|
|
- case spv::OpImageSampleProjDrefExplicitLod:
|
|
|
|
|
- case spv::OpImageDrefGather:
|
|
|
|
|
- case spv::OpImageSparseSampleDrefImplicitLod:
|
|
|
|
|
- case spv::OpImageSparseSampleDrefExplicitLod:
|
|
|
|
|
- case spv::OpImageSparseSampleProjDrefImplicitLod:
|
|
|
|
|
- case spv::OpImageSparseSampleProjDrefExplicitLod:
|
|
|
|
|
- case spv::OpImageSparseDrefGather:
|
|
|
|
|
- // Indicate that this variable was sampled with a dref sampler.
|
|
|
|
|
- {
|
|
|
|
|
- uint32_t var_id = _defs[op.args[2]]._origin_id;
|
|
|
|
|
- if (var_id != 0) {
|
|
|
|
|
- _defs[var_id]._flags |= DF_dref_sampled;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpBitcast:
|
|
|
|
|
- record_temporary(op.args[1], op.args[0], op.args[2], current_function_id);
|
|
|
|
|
-
|
|
|
|
|
- // Treat this like a load if it is casting to a non-pointer type.
|
|
|
|
|
- if (_defs[op.args[0]]._dtype != DT_type_pointer) {
|
|
|
|
|
- mark_used(op.args[1]);
|
|
|
|
|
- }
|
|
|
|
|
- // fall through, counts as unary arithmetic
|
|
|
|
|
- case spv::OpConvertFToU:
|
|
|
|
|
- case spv::OpConvertFToS:
|
|
|
|
|
- case spv::OpConvertSToF:
|
|
|
|
|
- case spv::OpConvertUToF:
|
|
|
|
|
- case spv::OpQuantizeToF16:
|
|
|
|
|
- case spv::OpSatConvertSToU:
|
|
|
|
|
- case spv::OpSatConvertUToS:
|
|
|
|
|
- case spv::OpConvertUToPtr:
|
|
|
|
|
- case spv::OpSNegate:
|
|
|
|
|
- case spv::OpFNegate:
|
|
|
|
|
- case spv::OpAny:
|
|
|
|
|
- case spv::OpAll:
|
|
|
|
|
- case spv::OpIsNan:
|
|
|
|
|
- case spv::OpIsInf:
|
|
|
|
|
- case spv::OpIsFinite:
|
|
|
|
|
- case spv::OpIsNormal:
|
|
|
|
|
- case spv::OpSignBitSet:
|
|
|
|
|
- case spv::OpNot:
|
|
|
|
|
- if ((_defs[op.args[2]]._flags & DF_constant_expression) != 0) {
|
|
|
|
|
- _defs[op.args[1]]._flags |= DF_constant_expression;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- // Binary arithmetic operators
|
|
|
|
|
- case spv::OpIAdd:
|
|
|
|
|
- case spv::OpFAdd:
|
|
|
|
|
- case spv::OpISub:
|
|
|
|
|
- case spv::OpFSub:
|
|
|
|
|
- case spv::OpIMul:
|
|
|
|
|
- case spv::OpFMul:
|
|
|
|
|
- case spv::OpUDiv:
|
|
|
|
|
- case spv::OpSDiv:
|
|
|
|
|
- case spv::OpFDiv:
|
|
|
|
|
- case spv::OpUMod:
|
|
|
|
|
- case spv::OpSRem:
|
|
|
|
|
- case spv::OpSMod:
|
|
|
|
|
- case spv::OpFRem:
|
|
|
|
|
- case spv::OpFMod:
|
|
|
|
|
- case spv::OpVectorTimesScalar:
|
|
|
|
|
- case spv::OpMatrixTimesScalar:
|
|
|
|
|
- case spv::OpVectorTimesMatrix:
|
|
|
|
|
- case spv::OpMatrixTimesVector:
|
|
|
|
|
- case spv::OpMatrixTimesMatrix:
|
|
|
|
|
- case spv::OpOuterProduct:
|
|
|
|
|
- case spv::OpDot:
|
|
|
|
|
- case spv::OpIAddCarry:
|
|
|
|
|
- case spv::OpISubBorrow:
|
|
|
|
|
- case spv::OpUMulExtended:
|
|
|
|
|
- case spv::OpSMulExtended:
|
|
|
|
|
- case spv::OpShiftRightLogical:
|
|
|
|
|
- case spv::OpShiftRightArithmetic:
|
|
|
|
|
- case spv::OpShiftLeftLogical:
|
|
|
|
|
- case spv::OpBitwiseOr:
|
|
|
|
|
- case spv::OpBitwiseXor:
|
|
|
|
|
- case spv::OpBitwiseAnd:
|
|
|
|
|
- if ((_defs[op.args[2]]._flags & DF_constant_expression) != 0 &&
|
|
|
|
|
- (_defs[op.args[3]]._flags & DF_constant_expression) != 0) {
|
|
|
|
|
- _defs[op.args[1]]._flags |= DF_constant_expression;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpSelect:
|
|
|
|
|
- // This can in theory operate on pointers, which is why we handle this
|
|
|
|
|
- //mark_used(op.args[2]);
|
|
|
|
|
- mark_used(op.args[3]);
|
|
|
|
|
- mark_used(op.args[4]);
|
|
|
|
|
-
|
|
|
|
|
- if ((_defs[op.args[2]]._flags & DF_constant_expression) != 0 &&
|
|
|
|
|
- (_defs[op.args[3]]._flags & DF_constant_expression) != 0 &&
|
|
|
|
|
- (_defs[op.args[4]]._flags & DF_constant_expression) != 0) {
|
|
|
|
|
- _defs[op.args[1]]._flags |= DF_constant_expression;
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpReturnValue:
|
|
|
|
|
- // A pointer can be returned when certain caps are present, so track it.
|
|
|
|
|
- mark_used(op.args[0]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- case spv::OpPtrEqual:
|
|
|
|
|
- case spv::OpPtrNotEqual:
|
|
|
|
|
- case spv::OpPtrDiff:
|
|
|
|
|
- // Consider a variable "used" if its pointer value is being compared, to be
|
|
|
|
|
- // on the safe side.
|
|
|
|
|
- mark_used(op.args[2]);
|
|
|
|
|
- mark_used(op.args[3]);
|
|
|
|
|
- break;
|
|
|
|
|
-
|
|
|
|
|
- default:
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Records that the given type has been defined.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-record_type(uint32_t id, const ShaderType *type) {
|
|
|
|
|
- Definition &def = modify_definition(id);
|
|
|
|
|
- def._dtype = DT_type;
|
|
|
|
|
- def._type = type;
|
|
|
|
|
-
|
|
|
|
|
- if (shader_cat.is_spam()) {
|
|
|
|
|
- if (type != nullptr) {
|
|
|
|
|
- shader_cat.spam()
|
|
|
|
|
- << "Defined type " << id << ": " << *type << "\n";
|
|
|
|
|
- } else {
|
|
|
|
|
- shader_cat.spam()
|
|
|
|
|
- << "Defined type " << id << ": void\n";
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (!def.has_builtin()) {
|
|
|
|
|
- // Only put types we can fully round-trip in the type map.
|
|
|
|
|
- _type_map[type] = id;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Records that the given type pointer has been defined.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-record_type_pointer(uint32_t id, spv::StorageClass storage_class, uint32_t type_id) {
|
|
|
|
|
- // Call modify_definition first, because it may invalidate references
|
|
|
|
|
- Definition &def = modify_definition(id);
|
|
|
|
|
-
|
|
|
|
|
- const Definition &type_def = get_definition(type_id);
|
|
|
|
|
- nassertv(type_def._dtype == DT_type || type_def._dtype == DT_type_pointer);
|
|
|
|
|
-
|
|
|
|
|
- def._dtype = DT_type_pointer;
|
|
|
|
|
- def._type = type_def._type;
|
|
|
|
|
- def._storage_class = storage_class;
|
|
|
|
|
- def._type_id = type_id;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Records that the given variable has been defined.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-record_variable(uint32_t id, uint32_t type_pointer_id, spv::StorageClass storage_class, uint32_t function_id) {
|
|
|
|
|
- // Call modify_definition first, because it may invalidate references
|
|
|
|
|
- Definition &def = modify_definition(id);
|
|
|
|
|
-
|
|
|
|
|
- const Definition &type_pointer_def = get_definition(type_pointer_id);
|
|
|
|
|
- if (type_pointer_def._dtype != DT_type_pointer && type_pointer_def._type_id != 0) {
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "Variable " << id << " should have valid pointer type\n";
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const Definition &type_def = get_definition(type_pointer_def._type_id);
|
|
|
|
|
- if (type_def._dtype != DT_type) {
|
|
|
|
|
- shader_cat.error()
|
|
|
|
|
- << "Type pointer " << type_pointer_id << " should point to valid type "
|
|
|
|
|
- "for variable " << id << "\n";
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // In older versions of SPIR-V, an SSBO was defined using BufferBlock.
|
|
|
|
|
- if (storage_class == spv::StorageClassUniform &&
|
|
|
|
|
- (type_def._flags & DF_buffer_block) != 0) {
|
|
|
|
|
- storage_class = spv::StorageClassStorageBuffer;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- def._dtype = DT_variable;
|
|
|
|
|
- def._type = type_def._type;
|
|
|
|
|
- def._type_id = type_pointer_id;
|
|
|
|
|
- def._storage_class = storage_class;
|
|
|
|
|
- def._origin_id = id;
|
|
|
|
|
- def._function_id = function_id;
|
|
|
|
|
-
|
|
|
|
|
- if (storage_class == spv::StorageClassStorageBuffer) {
|
|
|
|
|
- // Inherit readonly/writeonly from the variable but also from the struct.
|
|
|
|
|
- int flags = def._flags | type_def._flags;
|
|
|
|
|
- ShaderType::Access access = ShaderType::Access::read_write;
|
|
|
|
|
- if (flags & DF_non_writable) {
|
|
|
|
|
- access = (access & ShaderType::Access::read_only);
|
|
|
|
|
- }
|
|
|
|
|
- if (flags & DF_non_readable) {
|
|
|
|
|
- access = (access & ShaderType::Access::write_only);
|
|
|
|
|
- }
|
|
|
|
|
- def._type = ShaderType::register_type(ShaderType::StorageBuffer(def._type, access));
|
|
|
|
|
-
|
|
|
|
|
- if (shader_cat.is_debug()) {
|
|
|
|
|
- std::ostream &out = shader_cat.debug()
|
|
|
|
|
- << "Defined buffer " << id;
|
|
|
|
|
- if (!def._name.empty()) {
|
|
|
|
|
- out << ": " << def._name;
|
|
|
|
|
- }
|
|
|
|
|
- out << " with type " << *def._type << "\n";
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- else if (def._flags & (DF_non_writable | DF_non_readable)) {
|
|
|
|
|
- // If an image variable has the readonly/writeonly qualifiers, then we'll
|
|
|
|
|
- // inject those back into the type.
|
|
|
|
|
- if (const ShaderType::Image *image = def._type->as_image()) {
|
|
|
|
|
- ShaderType::Access access = image->get_access();
|
|
|
|
|
- if (def._flags & DF_non_writable) {
|
|
|
|
|
- access = (access & ShaderType::Access::read_only);
|
|
|
|
|
- }
|
|
|
|
|
- if (def._flags & DF_non_readable) {
|
|
|
|
|
- access = (access & ShaderType::Access::write_only);
|
|
|
|
|
- }
|
|
|
|
|
- if (access != image->get_access()) {
|
|
|
|
|
- def._type = ShaderType::register_type(ShaderType::Image(
|
|
|
|
|
- image->get_texture_type(),
|
|
|
|
|
- image->get_sampled_type(),
|
|
|
|
|
- access));
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-#ifndef NDEBUG
|
|
|
|
|
- if (storage_class == spv::StorageClassUniformConstant && shader_cat.is_debug()) {
|
|
|
|
|
- std::ostream &out = shader_cat.debug()
|
|
|
|
|
- << "Defined uniform " << id;
|
|
|
|
|
-
|
|
|
|
|
- if (!def._name.empty()) {
|
|
|
|
|
- out << ": " << def._name;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (def.has_location()) {
|
|
|
|
|
- out << " (location " << def._location << ")";
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- out << " with ";
|
|
|
|
|
-
|
|
|
|
|
- if (def._type != nullptr) {
|
|
|
|
|
- out << "type " << *def._type << "\n";
|
|
|
|
|
- } else {
|
|
|
|
|
- out << "unknown type\n";
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-#endif
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Records that the given function parameter has been defined.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-record_function_parameter(uint32_t id, uint32_t type_id, uint32_t function_id) {
|
|
|
|
|
- // Call modify_definition first, because it may invalidate references
|
|
|
|
|
- Definition &def = modify_definition(id);
|
|
|
|
|
-
|
|
|
|
|
- const Definition &type_def = get_definition(type_id);
|
|
|
|
|
- nassertv(type_def._dtype == DT_type || type_def._dtype == DT_type_pointer);
|
|
|
|
|
-
|
|
|
|
|
- def._dtype = DT_function_parameter;
|
|
|
|
|
- def._type = type_def._type;
|
|
|
|
|
- def._origin_id = id;
|
|
|
|
|
- def._function_id = function_id;
|
|
|
|
|
-
|
|
|
|
|
- nassertv(function_id != 0);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Records that the given constant has been defined.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-record_constant(uint32_t id, uint32_t type_id, const uint32_t *words, uint32_t nwords) {
|
|
|
|
|
- // Call modify_definition first, because it may invalidate references
|
|
|
|
|
- Definition &def = modify_definition(id);
|
|
|
|
|
-
|
|
|
|
|
- const Definition &type_def = get_definition(type_id);
|
|
|
|
|
-
|
|
|
|
|
- def._dtype = DT_constant;
|
|
|
|
|
- def._type_id = type_id;
|
|
|
|
|
- def._type = (type_def._dtype == DT_type) ? type_def._type : nullptr;
|
|
|
|
|
- def._constant = (nwords > 0) ? words[0] : 0;
|
|
|
|
|
- def._flags |= DF_constant_expression;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Records an external import.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-record_ext_inst_import(uint32_t id, const char *import) {
|
|
|
|
|
- Definition &def = modify_definition(id);
|
|
|
|
|
- def._dtype = DT_ext_inst;
|
|
|
|
|
- def._name.assign(import);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Records that the given function has been defined.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-record_function(uint32_t id, uint32_t type_id) {
|
|
|
|
|
- // Call modify_definition first, because it may invalidate references
|
|
|
|
|
- Definition &def = modify_definition(id);
|
|
|
|
|
-
|
|
|
|
|
- const Definition &type_def = get_definition(type_id);
|
|
|
|
|
-
|
|
|
|
|
- def._dtype = DT_function;
|
|
|
|
|
- def._type = type_def._type;
|
|
|
|
|
- def._type_id = type_id;
|
|
|
|
|
- def._function_id = id;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Record a temporary. We mostly use this to record the chain of loads and
|
|
|
|
|
- * copies so that we can figure out whether (and how) a given variable is used.
|
|
|
|
|
- *
|
|
|
|
|
- * from_id indicates from what this variable is initialized or generated, for
|
|
|
|
|
- * the purpose of transitively tracking usage.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-record_temporary(uint32_t id, uint32_t type_id, uint32_t from_id, uint32_t function_id) {
|
|
|
|
|
- // Call modify_definition first, because it may invalidate references
|
|
|
|
|
- Definition &def = modify_definition(id);
|
|
|
|
|
-
|
|
|
|
|
- const Definition &type_def = get_definition(type_id);
|
|
|
|
|
- const Definition &from_def = get_definition(from_id);
|
|
|
|
|
-
|
|
|
|
|
- def._dtype = DT_temporary;
|
|
|
|
|
- def._type = type_def._type;
|
|
|
|
|
- def._type_id = type_id;
|
|
|
|
|
- def._origin_id = from_def._origin_id;
|
|
|
|
|
- def._function_id = function_id;
|
|
|
|
|
-
|
|
|
|
|
- nassertv(function_id != 0);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Records that the given specialization constant has been defined.
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-record_spec_constant(uint32_t id, uint32_t type_id) {
|
|
|
|
|
- // Call modify_definition first, because it may invalidate references
|
|
|
|
|
- Definition &def = modify_definition(id);
|
|
|
|
|
-
|
|
|
|
|
- const Definition &type_def = get_definition(type_id);
|
|
|
|
|
- nassertv(type_def._dtype == DT_type);
|
|
|
|
|
-
|
|
|
|
|
- def._dtype = DT_spec_constant;
|
|
|
|
|
- def._type_id = type_id;
|
|
|
|
|
- def._type = type_def._type;
|
|
|
|
|
- def._flags |= DF_constant_expression;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Called for a variable, or any id whose value (indirectly) originates from a
|
|
|
|
|
- * variable, to mark the variable and any types used thereby as "used".
|
|
|
|
|
- */
|
|
|
|
|
-void ShaderModuleSpirV::InstructionWriter::
|
|
|
|
|
-mark_used(uint32_t id) {
|
|
|
|
|
- uint32_t origin_id = _defs[id]._origin_id;
|
|
|
|
|
- if (origin_id != 0) {
|
|
|
|
|
- Definition &origin_def = _defs[origin_id];
|
|
|
|
|
- if (!origin_def.is_used()) {
|
|
|
|
|
- origin_def._flags |= DF_used;
|
|
|
|
|
-
|
|
|
|
|
- // Also mark the type pointer as used.
|
|
|
|
|
- if (origin_def._type_id != 0) {
|
|
|
|
|
- Definition &type_pointer_def = _defs[origin_def._type_id];
|
|
|
|
|
- type_pointer_def._flags |= DF_used;
|
|
|
|
|
-
|
|
|
|
|
- // And the type that references.
|
|
|
|
|
- if (type_pointer_def._type_id != 0) {
|
|
|
|
|
- Definition &type_def = _defs[type_pointer_def._type_id];
|
|
|
|
|
- type_def._flags |= DF_used;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|