|
|
@@ -26,6 +26,9 @@
|
|
|
#include <Cg/cg.h>
|
|
|
#endif
|
|
|
|
|
|
+#include <vulkan/spirv.h>
|
|
|
+#include <stdint.h>
|
|
|
+
|
|
|
TypeHandle Shader::_type_handle;
|
|
|
Shader::ShaderTable Shader::_load_table;
|
|
|
Shader::ShaderTable Shader::_make_table;
|
|
|
@@ -2280,6 +2283,11 @@ do_read_source(string &into, const Filename &fn, BamCacheRecord *record) {
|
|
|
_last_modified = max(_last_modified, vf->get_timestamp());
|
|
|
_source_files.push_back(vf->get_filename());
|
|
|
}
|
|
|
+
|
|
|
+ if (_language == SL_SPIR_V) {
|
|
|
+ return spirv_analyze_shader(into);
|
|
|
+ }
|
|
|
+
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
@@ -2468,6 +2476,237 @@ check_modified() const {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * Parses a SPIR-V shader to figure out which parameters it uses.
|
|
|
+ */
|
|
|
+bool Shader::
|
|
|
+spirv_analyze_shader(const string &data) {
|
|
|
+ //const string &data = get_text(type);
|
|
|
+ const uint32_t *words = (const uint32_t *)data.data();
|
|
|
+ const size_t length = data.size() >> 2;
|
|
|
+ const uint32_t *end = words + length;
|
|
|
+
|
|
|
+ if (length < 5) {
|
|
|
+ shader_cat.error()
|
|
|
+ << "Invalid SPIR-V file: too short.\n";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (*words++ != SpvMagicNumber) {
|
|
|
+ shader_cat.error()
|
|
|
+ << "Invalid SPIR-V file: wrong magic number.\n";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ++words; // version
|
|
|
+ ++words; // generator
|
|
|
+ uint32_t bound = *words++;
|
|
|
+ ++words; // schema (reserved)
|
|
|
+
|
|
|
+ // Stores information on all of the types and variables encountered.
|
|
|
+ struct ShaderVar {
|
|
|
+ string _name;
|
|
|
+ ShaderArgType _type;
|
|
|
+ int _location;
|
|
|
+ } def_var = {string(), SAT_unknown, -1};
|
|
|
+
|
|
|
+ pvector<ShaderVar> vars(bound, def_var);
|
|
|
+
|
|
|
+ ShaderType shader_type = ST_none;
|
|
|
+
|
|
|
+ while (words < end) {
|
|
|
+ uint16_t wcount = words[0] >> 16;
|
|
|
+ SpvOp opcode = (SpvOp)(words[0] & 0xffff);
|
|
|
+
|
|
|
+ switch (opcode) {
|
|
|
+ case SpvOpMemoryModel:
|
|
|
+ nassertr(wcount == 3, false);
|
|
|
+ if (words[1] != SpvAddressingModelLogical) {
|
|
|
+ shader_cat.error()
|
|
|
+ << "Invalid SPIR-V shader: addressing model Logical must be used.\n";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (words[2] != SpvMemoryModelGLSL450) {
|
|
|
+ shader_cat.error()
|
|
|
+ << "Invalid SPIR-V shader: memory model GLSL450 must be used.\n";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case SpvOpEntryPoint:
|
|
|
+ switch ((SpvExecutionModel)words[1]) {
|
|
|
+ case SpvExecutionModelVertex:
|
|
|
+ shader_type = ST_vertex;
|
|
|
+ break;
|
|
|
+ case SpvExecutionModelTessellationControl:
|
|
|
+ shader_type = ST_tess_control;
|
|
|
+ break;
|
|
|
+ case SpvExecutionModelTessellationEvaluation:
|
|
|
+ shader_type = ST_tess_evaluation;
|
|
|
+ break;
|
|
|
+ case SpvExecutionModelGeometry:
|
|
|
+ shader_type = ST_geometry;
|
|
|
+ break;
|
|
|
+ case SpvExecutionModelFragment:
|
|
|
+ shader_type = ST_fragment;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case SpvOpName:
|
|
|
+ vars[words[1]]._name = string((const char *)&words[2]);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case SpvOpTypeVoid:
|
|
|
+ vars[words[1]]._type = SAT_unknown;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case SpvOpTypeFloat:
|
|
|
+ vars[words[1]]._type = SAT_scalar;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case SpvOpTypeVector:
|
|
|
+ vars[words[1]]._type = (ShaderArgType)(SAT_vec1 + (words[3] - 1));
|
|
|
+ break;
|
|
|
+
|
|
|
+ case SpvOpTypeMatrix:
|
|
|
+ vars[words[1]]._type = (ShaderArgType)(SAT_mat1x1 + (words[3] - 1) * 5);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case SpvOpTypePointer:
|
|
|
+ // A type and type pointer isn't the same, but this is how types of
|
|
|
+ // variables are referenced, and it's convenient to do this since
|
|
|
+ // the "Logical" address model doesn't support using pointers anyway.
|
|
|
+ vars[words[1]]._type = vars[words[3]]._type;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case SpvOpTypeImage:
|
|
|
+ switch ((SpvDim)words[3]) {
|
|
|
+ case SpvDim1D:
|
|
|
+ vars[words[1]]._type = SAT_sampler1d;
|
|
|
+ break;
|
|
|
+ case SpvDim2D:
|
|
|
+ if (words[5]) {
|
|
|
+ vars[words[1]]._type = SAT_sampler2d_array;
|
|
|
+ } else {
|
|
|
+ vars[words[1]]._type = SAT_sampler2d;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case SpvDim3D:
|
|
|
+ vars[words[1]]._type = SAT_sampler3d;
|
|
|
+ break;
|
|
|
+ case SpvDimCube:
|
|
|
+ if (words[5]) {
|
|
|
+ vars[words[1]]._type = SAT_sampler_cube_array;
|
|
|
+ } else {
|
|
|
+ vars[words[1]]._type = SAT_sampler_cube;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case SpvDimRect:
|
|
|
+ shader_cat.error()
|
|
|
+ << "imageRect shader inputs are not supported.\n";
|
|
|
+ return false;
|
|
|
+ case SpvDimBuffer:
|
|
|
+ vars[words[1]]._type = SAT_sampler_buffer;
|
|
|
+ break;
|
|
|
+ case SpvDimSubpassData:
|
|
|
+ shader_cat.error()
|
|
|
+ << "subpassInput shader inputs are not supported.\n";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case SpvOpTypeSampler:
|
|
|
+ // A sampler that's not bound to a particular image.
|
|
|
+ vars[words[1]]._type = SAT_sampler;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case SpvOpTypeSampledImage:
|
|
|
+ // We don't currently distinguish between samplers and images.
|
|
|
+ vars[words[1]]._type = vars[words[2]]._type;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case SpvOpVariable:
|
|
|
+ // A variable definition - check the storage class.
|
|
|
+ switch ((SpvStorageClass)words[3]) {
|
|
|
+ case SpvStorageClassUniformConstant:
|
|
|
+ {
|
|
|
+ if (vars[words[1]]._type < SAT_sampler1d) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ const ShaderVar &var = vars[words[2]];
|
|
|
+ ShaderTexSpec spec;
|
|
|
+ spec._id._name = var._name;
|
|
|
+ spec._id._type = shader_type;
|
|
|
+ spec._id._seqno = var._location;
|
|
|
+ spec._part = STO_named_input;
|
|
|
+ spec._stage = -1;
|
|
|
+ spec._desired_type = -1;
|
|
|
+ _tex_spec.push_back(spec);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case SpvStorageClassInput:
|
|
|
+ if (shader_type == ST_vertex) {
|
|
|
+ const ShaderVar &var = vars[words[2]];
|
|
|
+ if (var._name.empty()) {
|
|
|
+ shader_cat.error()
|
|
|
+ << "Shader vertex input requires a name.\n";
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (var._location < 0) {
|
|
|
+ //TODO: just jam one in here somewhere?
|
|
|
+ shader_cat.error()
|
|
|
+ << "Shader vertex input requires location decoration.\n";
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ ShaderVarSpec spec;
|
|
|
+ spec._id._name = var._name;
|
|
|
+ spec._id._type = shader_type;
|
|
|
+ spec._id._seqno = var._location;
|
|
|
+ spec._name = InternalName::make(var._name);
|
|
|
+ spec._append_uv = false;
|
|
|
+ spec._elements = 1;
|
|
|
+ spec._integer = false;
|
|
|
+ _var_spec.push_back(spec);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case SpvStorageClassUniform:
|
|
|
+ case SpvStorageClassOutput:
|
|
|
+ case SpvStorageClassWorkgroup:
|
|
|
+ case SpvStorageClassCrossWorkgroup:
|
|
|
+ case SpvStorageClassPrivate:
|
|
|
+ case SpvStorageClassFunction:
|
|
|
+ case SpvStorageClassGeneric:
|
|
|
+ case SpvStorageClassPushConstant:
|
|
|
+ case SpvStorageClassAtomicCounter:
|
|
|
+ case SpvStorageClassImage:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case SpvOpDecorate:
|
|
|
+ // Use the same field to store location/binding, since I don't believe
|
|
|
+ // that variables can specify both.
|
|
|
+ if (words[2] == SpvDecorationLocation || words[2] == SpvDecorationBinding) {
|
|
|
+ vars[words[1]]._location = words[3];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ words += wcount;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (shader_type == ST_none) {
|
|
|
+ shader_cat.error()
|
|
|
+ << "No valid entry point found in SPIR-V shader.\n";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
#ifdef HAVE_CG
|
|
|
/**
|
|
|
* Determines the appropriate active shader profile settings based on any
|