Merge pull request #48 from KhronosGroup/combined-image-samplers
Support separate image samplers in GLSL/ESSL
This commit is contained in:
commit
41f9fd9ef0
18
main.cpp
18
main.cpp
@ -317,7 +317,7 @@ static void print_resources(const Compiler &compiler, const ShaderResources &res
|
||||
print_resources(compiler, "inputs", res.stage_inputs);
|
||||
print_resources(compiler, "outputs", res.stage_outputs);
|
||||
print_resources(compiler, "textures", res.sampled_images);
|
||||
print_resources(compiler, "separate textures", res.separate_images);
|
||||
print_resources(compiler, "separate images", res.separate_images);
|
||||
print_resources(compiler, "separate samplers", res.separate_samplers);
|
||||
print_resources(compiler, "images", res.storage_images);
|
||||
print_resources(compiler, "ssbos", res.storage_buffers);
|
||||
@ -554,6 +554,8 @@ int main(int argc, char *argv[])
|
||||
|
||||
unique_ptr<CompilerGLSL> compiler;
|
||||
|
||||
bool combined_image_samplers = false;
|
||||
|
||||
if (args.cpp)
|
||||
{
|
||||
compiler = unique_ptr<CompilerGLSL>(new CompilerCPP(read_spirv_file(args.input)));
|
||||
@ -563,7 +565,10 @@ int main(int argc, char *argv[])
|
||||
else if (args.metal)
|
||||
compiler = unique_ptr<CompilerMSL>(new CompilerMSL(read_spirv_file(args.input)));
|
||||
else
|
||||
{
|
||||
combined_image_samplers = !args.vulkan_semantics;
|
||||
compiler = unique_ptr<CompilerGLSL>(new CompilerGLSL(read_spirv_file(args.input)));
|
||||
}
|
||||
|
||||
if (!args.entry.empty())
|
||||
compiler->set_entry_point(args.entry);
|
||||
@ -622,6 +627,17 @@ int main(int argc, char *argv[])
|
||||
print_push_constant_resources(*compiler, res.push_constant_buffers);
|
||||
}
|
||||
|
||||
if (combined_image_samplers)
|
||||
{
|
||||
compiler->build_combined_image_samplers();
|
||||
// Give the remapped combined samplers new names.
|
||||
for (auto &remap : compiler->get_combined_image_samplers())
|
||||
{
|
||||
compiler->set_name(remap.combined_id, join("SPIRV_Cross_Combined", compiler->get_name(remap.image_id),
|
||||
compiler->get_name(remap.sampler_id)));
|
||||
}
|
||||
}
|
||||
|
||||
string glsl;
|
||||
for (uint32_t i = 0; i < args.iterations; i++)
|
||||
glsl = compiler->compile();
|
||||
|
@ -0,0 +1,48 @@
|
||||
#version 310 es
|
||||
precision mediump float;
|
||||
precision highp int;
|
||||
|
||||
uniform mediump sampler2D SPIRV_Cross_CombineduTexture0uSampler0;
|
||||
uniform mediump sampler2D SPIRV_Cross_CombineduTexture1uSampler1;
|
||||
uniform mediump sampler2D SPIRV_Cross_CombineduTexture1uSampler0;
|
||||
uniform mediump sampler2D SPIRV_Cross_CombineduTexture0uSampler1;
|
||||
|
||||
layout(location = 0) in vec2 vTex;
|
||||
layout(location = 0) out vec4 FragColor;
|
||||
|
||||
vec4 sample_dual(mediump sampler2D SPIRV_Cross_Combinedtexsamp)
|
||||
{
|
||||
return texture(SPIRV_Cross_Combinedtexsamp, vTex);
|
||||
}
|
||||
|
||||
vec4 sample_duals()
|
||||
{
|
||||
vec4 a = sample_dual(SPIRV_Cross_CombineduTexture0uSampler0);
|
||||
vec4 b = sample_dual(SPIRV_Cross_CombineduTexture1uSampler1);
|
||||
return (a + b);
|
||||
}
|
||||
|
||||
vec4 sample_global_tex(mediump sampler2D SPIRV_Cross_CombineduTexture0samp, mediump sampler2D SPIRV_Cross_CombineduTexture1samp)
|
||||
{
|
||||
vec4 a = texture(SPIRV_Cross_CombineduTexture0samp, vTex);
|
||||
vec4 b = sample_dual(SPIRV_Cross_CombineduTexture1samp);
|
||||
return (a + b);
|
||||
}
|
||||
|
||||
vec4 sample_global_sampler(mediump sampler2D SPIRV_Cross_CombinedtexuSampler0, mediump sampler2D SPIRV_Cross_CombinedtexuSampler1)
|
||||
{
|
||||
vec4 a = texture(SPIRV_Cross_CombinedtexuSampler0, vTex);
|
||||
vec4 b = sample_dual(SPIRV_Cross_CombinedtexuSampler1);
|
||||
return (a + b);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 c0 = sample_duals();
|
||||
vec4 c1 = sample_global_tex(SPIRV_Cross_CombineduTexture0uSampler0, SPIRV_Cross_CombineduTexture1uSampler0);
|
||||
vec4 c2 = sample_global_tex(SPIRV_Cross_CombineduTexture0uSampler1, SPIRV_Cross_CombineduTexture1uSampler1);
|
||||
vec4 c3 = sample_global_sampler(SPIRV_Cross_CombineduTexture0uSampler0, SPIRV_Cross_CombineduTexture0uSampler1);
|
||||
vec4 c4 = sample_global_sampler(SPIRV_Cross_CombineduTexture1uSampler0, SPIRV_Cross_CombineduTexture1uSampler1);
|
||||
FragColor = ((((c0 + c1) + c2) + c3) + c4);
|
||||
}
|
||||
|
@ -0,0 +1,48 @@
|
||||
#version 310 es
|
||||
precision mediump float;
|
||||
precision highp int;
|
||||
|
||||
layout(set = 0, binding = 2) uniform mediump texture2D uTexture0;
|
||||
layout(set = 0, binding = 3) uniform mediump texture2D uTexture1;
|
||||
layout(set = 0, binding = 0) uniform mediump sampler uSampler0;
|
||||
layout(set = 0, binding = 1) uniform mediump sampler uSampler1;
|
||||
|
||||
layout(location = 0) in vec2 vTex;
|
||||
layout(location = 0) out vec4 FragColor;
|
||||
|
||||
vec4 sample_dual(mediump sampler samp, mediump texture2D tex)
|
||||
{
|
||||
return texture(sampler2D(tex, samp), vTex);
|
||||
}
|
||||
|
||||
vec4 sample_duals()
|
||||
{
|
||||
vec4 a = sample_dual(uSampler0, uTexture0);
|
||||
vec4 b = sample_dual(uSampler1, uTexture1);
|
||||
return (a + b);
|
||||
}
|
||||
|
||||
vec4 sample_global_tex(mediump sampler samp)
|
||||
{
|
||||
vec4 a = texture(sampler2D(uTexture0, samp), vTex);
|
||||
vec4 b = sample_dual(samp, uTexture1);
|
||||
return (a + b);
|
||||
}
|
||||
|
||||
vec4 sample_global_sampler(mediump texture2D tex)
|
||||
{
|
||||
vec4 a = texture(sampler2D(tex, uSampler0), vTex);
|
||||
vec4 b = sample_dual(uSampler1, tex);
|
||||
return (a + b);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 c0 = sample_duals();
|
||||
vec4 c1 = sample_global_tex(uSampler0);
|
||||
vec4 c2 = sample_global_tex(uSampler1);
|
||||
vec4 c3 = sample_global_sampler(uTexture0);
|
||||
vec4 c4 = sample_global_sampler(uTexture1);
|
||||
FragColor = ((((c0 + c1) + c2) + c3) + c4);
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
#version 310 es
|
||||
precision mediump float;
|
||||
precision highp int;
|
||||
|
||||
layout(binding = 1) uniform mediump texture2D uTexture;
|
||||
layout(binding = 0) uniform mediump sampler uSampler;
|
||||
layout(binding = 4) uniform mediump texture2DArray uTextureArray;
|
||||
layout(binding = 3) uniform mediump textureCube uTextureCube;
|
||||
layout(binding = 2) uniform mediump texture3D uTexture3D;
|
||||
|
||||
layout(location = 0) in vec2 vTex;
|
||||
layout(location = 1) in vec3 vTex3;
|
||||
layout(location = 0) out vec4 FragColor;
|
||||
|
||||
vec4 sample_func(mediump sampler samp, vec2 uv)
|
||||
{
|
||||
return texture(sampler2D(uTexture, samp), uv);
|
||||
}
|
||||
|
||||
vec4 sample_func_dual(mediump sampler samp, mediump texture2D tex, vec2 uv)
|
||||
{
|
||||
return texture(sampler2D(tex, samp), uv);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 off = (vec2(1.0) / vec2(textureSize(sampler2D(uTexture, uSampler), 0)));
|
||||
vec2 off2 = (vec2(1.0) / vec2(textureSize(sampler2D(uTexture, uSampler), 1)));
|
||||
highp vec2 param = ((vTex + off) + off2);
|
||||
vec4 c0 = sample_func(uSampler, param);
|
||||
highp vec2 param_1 = ((vTex + off) + off2);
|
||||
vec4 c1 = sample_func_dual(uSampler, uTexture, param_1);
|
||||
vec4 c2 = texture(sampler2DArray(uTextureArray, uSampler), vTex3);
|
||||
vec4 c3 = texture(samplerCube(uTextureCube, uSampler), vTex3);
|
||||
vec4 c4 = texture(sampler3D(uTexture3D, uSampler), vTex3);
|
||||
FragColor = ((((c0 + c1) + c2) + c3) + c4);
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
#version 310 es
|
||||
precision mediump float;
|
||||
precision highp int;
|
||||
|
||||
uniform mediump sampler2D SPIRV_Cross_CombineduTextureuSampler;
|
||||
uniform mediump sampler2DArray SPIRV_Cross_CombineduTextureArrayuSampler;
|
||||
uniform mediump samplerCube SPIRV_Cross_CombineduTextureCubeuSampler;
|
||||
uniform mediump sampler3D SPIRV_Cross_CombineduTexture3DuSampler;
|
||||
|
||||
layout(location = 0) in vec2 vTex;
|
||||
layout(location = 1) in vec3 vTex3;
|
||||
layout(location = 0) out vec4 FragColor;
|
||||
|
||||
vec4 sample_func(vec2 uv, mediump sampler2D SPIRV_Cross_CombineduTexturesamp)
|
||||
{
|
||||
return texture(SPIRV_Cross_CombineduTexturesamp, uv);
|
||||
}
|
||||
|
||||
vec4 sample_func_dual(vec2 uv, mediump sampler2D SPIRV_Cross_Combinedtexsamp)
|
||||
{
|
||||
return texture(SPIRV_Cross_Combinedtexsamp, uv);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 off = (vec2(1.0) / vec2(textureSize(SPIRV_Cross_CombineduTextureuSampler, 0)));
|
||||
vec2 off2 = (vec2(1.0) / vec2(textureSize(SPIRV_Cross_CombineduTextureuSampler, 1)));
|
||||
highp vec2 param = ((vTex + off) + off2);
|
||||
vec4 c0 = sample_func(param, SPIRV_Cross_CombineduTextureuSampler);
|
||||
highp vec2 param_1 = ((vTex + off) + off2);
|
||||
vec4 c1 = sample_func_dual(param_1, SPIRV_Cross_CombineduTextureuSampler);
|
||||
vec4 c2 = texture(SPIRV_Cross_CombineduTextureArrayuSampler, vTex3);
|
||||
vec4 c3 = texture(SPIRV_Cross_CombineduTextureCubeuSampler, vTex3);
|
||||
vec4 c4 = texture(SPIRV_Cross_CombineduTexture3DuSampler, vTex3);
|
||||
FragColor = ((((c0 + c1) + c2) + c3) + c4);
|
||||
}
|
||||
|
47
shaders/vulkan/frag/combined-texture-sampler.vk.frag
Normal file
47
shaders/vulkan/frag/combined-texture-sampler.vk.frag
Normal file
@ -0,0 +1,47 @@
|
||||
#version 310 es
|
||||
precision mediump float;
|
||||
|
||||
layout(set = 0, binding = 0) uniform mediump sampler uSampler0;
|
||||
layout(set = 0, binding = 1) uniform mediump sampler uSampler1;
|
||||
layout(set = 0, binding = 2) uniform mediump texture2D uTexture0;
|
||||
layout(set = 0, binding = 3) uniform mediump texture2D uTexture1;
|
||||
|
||||
layout(location = 0) out vec4 FragColor;
|
||||
layout(location = 0) in vec2 vTex;
|
||||
|
||||
vec4 sample_dual(mediump sampler samp, mediump texture2D tex)
|
||||
{
|
||||
return texture(sampler2D(tex, samp), vTex);
|
||||
}
|
||||
|
||||
vec4 sample_global_tex(mediump sampler samp)
|
||||
{
|
||||
vec4 a = texture(sampler2D(uTexture0, samp), vTex);
|
||||
vec4 b = sample_dual(samp, uTexture1);
|
||||
return a + b;
|
||||
}
|
||||
|
||||
vec4 sample_global_sampler(mediump texture2D tex)
|
||||
{
|
||||
vec4 a = texture(sampler2D(tex, uSampler0), vTex);
|
||||
vec4 b = sample_dual(uSampler1, tex);
|
||||
return a + b;
|
||||
}
|
||||
|
||||
vec4 sample_duals()
|
||||
{
|
||||
vec4 a = sample_dual(uSampler0, uTexture0);
|
||||
vec4 b = sample_dual(uSampler1, uTexture1);
|
||||
return a + b;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 c0 = sample_duals();
|
||||
vec4 c1 = sample_global_tex(uSampler0);
|
||||
vec4 c2 = sample_global_tex(uSampler1);
|
||||
vec4 c3 = sample_global_sampler(uTexture0);
|
||||
vec4 c4 = sample_global_sampler(uTexture1);
|
||||
|
||||
FragColor = c0 + c1 + c2 + c3 + c4;
|
||||
}
|
@ -439,12 +439,34 @@ struct SPIRFunction : IVariant
|
||||
uint32_t write_count;
|
||||
};
|
||||
|
||||
// When calling a function, and we're remapping separate image samplers,
|
||||
// resolve these arguments into combined image samplers and pass them
|
||||
// as additional arguments in this order.
|
||||
// It gets more complicated as functions can pull in their own globals
|
||||
// and combine them with parameters,
|
||||
// so we need to distinguish if something is local parameter index
|
||||
// or a global ID.
|
||||
struct CombinedImageSamplerParameter
|
||||
{
|
||||
uint32_t id;
|
||||
uint32_t image_id;
|
||||
uint32_t sampler_id;
|
||||
bool global_image;
|
||||
bool global_sampler;
|
||||
};
|
||||
|
||||
uint32_t return_type;
|
||||
uint32_t function_type;
|
||||
std::vector<Parameter> arguments;
|
||||
|
||||
// Can be used by backends to add magic arguments.
|
||||
// Currently used by combined image/sampler implementation.
|
||||
|
||||
std::vector<Parameter> shadow_arguments;
|
||||
std::vector<uint32_t> local_variables;
|
||||
uint32_t entry_block = 0;
|
||||
std::vector<uint32_t> blocks;
|
||||
std::vector<CombinedImageSamplerParameter> combined_parameters;
|
||||
|
||||
void add_local_variable(uint32_t id)
|
||||
{
|
||||
@ -459,6 +481,7 @@ struct SPIRFunction : IVariant
|
||||
|
||||
bool active = false;
|
||||
bool flush_undeclared = true;
|
||||
bool do_combined_parameters = true;
|
||||
};
|
||||
|
||||
struct SPIRVariable : IVariant
|
||||
|
336
spirv_cross.cpp
336
spirv_cross.cpp
@ -382,6 +382,14 @@ bool Compiler::is_hidden_variable(const SPIRVariable &var, bool include_builtins
|
||||
if ((is_builtin_variable(var) && !include_builtins) || var.remapped_variable)
|
||||
return true;
|
||||
|
||||
// Combined image samplers are always considered active as they are "magic" variables.
|
||||
if (find_if(begin(combined_image_samplers), end(combined_image_samplers), [&var](const CombinedImageSampler &samp) {
|
||||
return samp.combined_id == var.self;
|
||||
}) != end(combined_image_samplers))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hidden = false;
|
||||
if (check_active_interface_variables && storage_class_is_interface(var.storage))
|
||||
hidden = active_interface_variables.find(var.self) == end(active_interface_variables);
|
||||
@ -1913,8 +1921,15 @@ bool Compiler::traverse_all_reachable_opcodes(const SPIRBlock &block, OpcodeHand
|
||||
if (!handler.handle(op, ops, i.length))
|
||||
return false;
|
||||
|
||||
if (op == OpFunctionCall && !traverse_all_reachable_opcodes(get<SPIRFunction>(ops[2]), handler))
|
||||
return false;
|
||||
if (op == OpFunctionCall)
|
||||
{
|
||||
if (!handler.begin_function_scope(ops, i.length))
|
||||
return false;
|
||||
if (!traverse_all_reachable_opcodes(get<SPIRFunction>(ops[2]), handler))
|
||||
return false;
|
||||
if (!handler.end_function_scope(ops, i.length))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -2072,11 +2087,11 @@ std::vector<BufferRange> Compiler::get_active_buffer_ranges(uint32_t id) const
|
||||
// Returns the value of the first ID available for use in the expanded bound.
|
||||
uint32_t Compiler::increase_bound_by(uint32_t incr_amount)
|
||||
{
|
||||
uint32_t curr_bound = (uint32_t)ids.size();
|
||||
uint32_t new_bound = curr_bound + incr_amount;
|
||||
auto curr_bound = ids.size();
|
||||
auto new_bound = curr_bound + incr_amount;
|
||||
ids.resize(new_bound);
|
||||
meta.resize(new_bound);
|
||||
return curr_bound;
|
||||
return uint32_t(curr_bound);
|
||||
}
|
||||
|
||||
bool Compiler::types_are_logically_equivalent(const SPIRType &a, const SPIRType &b) const
|
||||
@ -2290,3 +2305,314 @@ bool Compiler::interface_variable_exists_in_entry_point(uint32_t id) const
|
||||
return find(begin(execution.interface_variables), end(execution.interface_variables), id) !=
|
||||
end(execution.interface_variables);
|
||||
}
|
||||
|
||||
void Compiler::CombinedImageSamplerHandler::push_remap_parameters(const SPIRFunction &func, const uint32_t *args,
|
||||
uint32_t length)
|
||||
{
|
||||
// If possible, pipe through a remapping table so that parameters know
|
||||
// which variables they actually bind to in this scope.
|
||||
unordered_map<uint32_t, uint32_t> remapping;
|
||||
for (uint32_t i = 0; i < length; i++)
|
||||
remapping[func.arguments[i].id] = remap_parameter(args[i]);
|
||||
parameter_remapping.push(move(remapping));
|
||||
}
|
||||
|
||||
void Compiler::CombinedImageSamplerHandler::pop_remap_parameters()
|
||||
{
|
||||
parameter_remapping.pop();
|
||||
}
|
||||
|
||||
uint32_t Compiler::CombinedImageSamplerHandler::remap_parameter(uint32_t id)
|
||||
{
|
||||
auto *var = compiler.maybe_get_backing_variable(id);
|
||||
if (var)
|
||||
id = var->self;
|
||||
|
||||
if (parameter_remapping.empty())
|
||||
return id;
|
||||
|
||||
auto &remapping = parameter_remapping.top();
|
||||
auto itr = remapping.find(id);
|
||||
if (itr != end(remapping))
|
||||
return itr->second;
|
||||
else
|
||||
return id;
|
||||
}
|
||||
|
||||
bool Compiler::CombinedImageSamplerHandler::begin_function_scope(const uint32_t *args, uint32_t length)
|
||||
{
|
||||
if (length < 3)
|
||||
return false;
|
||||
|
||||
auto &callee = compiler.get<SPIRFunction>(args[2]);
|
||||
args += 3;
|
||||
length -= 3;
|
||||
push_remap_parameters(callee, args, length);
|
||||
functions.push(&callee);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Compiler::CombinedImageSamplerHandler::end_function_scope(const uint32_t *args, uint32_t length)
|
||||
{
|
||||
if (length < 3)
|
||||
return false;
|
||||
|
||||
auto &callee = compiler.get<SPIRFunction>(args[2]);
|
||||
args += 3;
|
||||
length -= 3;
|
||||
|
||||
// There are two types of cases we have to handle,
|
||||
// a callee might call sampler2D(texture2D, sampler) directly where
|
||||
// one or more parameters originate from parameters.
|
||||
// Alternatively, we need to provide combined image samplers to our callees,
|
||||
// and in this case we need to add those as well.
|
||||
|
||||
pop_remap_parameters();
|
||||
|
||||
// Our callee has now been processed at least once.
|
||||
// No point in doing it again.
|
||||
callee.do_combined_parameters = false;
|
||||
|
||||
auto ¶ms = functions.top()->combined_parameters;
|
||||
functions.pop();
|
||||
if (functions.empty())
|
||||
return true;
|
||||
|
||||
auto &caller = *functions.top();
|
||||
if (caller.do_combined_parameters)
|
||||
{
|
||||
for (auto ¶m : params)
|
||||
{
|
||||
uint32_t image_id = param.global_image ? param.image_id : args[param.image_id];
|
||||
uint32_t sampler_id = param.global_sampler ? param.sampler_id : args[param.sampler_id];
|
||||
|
||||
auto *i = compiler.maybe_get_backing_variable(image_id);
|
||||
auto *s = compiler.maybe_get_backing_variable(sampler_id);
|
||||
if (i)
|
||||
image_id = i->self;
|
||||
if (s)
|
||||
sampler_id = s->self;
|
||||
|
||||
register_combined_image_sampler(caller, image_id, sampler_id);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Compiler::CombinedImageSamplerHandler::register_combined_image_sampler(SPIRFunction &caller, uint32_t image_id,
|
||||
uint32_t sampler_id)
|
||||
{
|
||||
// We now have a texture ID and a sampler ID which will either be found as a global
|
||||
// or a parameter in our own function. If both are global, they will not need a parameter,
|
||||
// otherwise, add it to our list.
|
||||
SPIRFunction::CombinedImageSamplerParameter param = {
|
||||
0u, image_id, sampler_id, true, true,
|
||||
};
|
||||
|
||||
auto texture_itr = find_if(begin(caller.arguments), end(caller.arguments),
|
||||
[image_id](const SPIRFunction::Parameter &p) { return p.id == image_id; });
|
||||
auto sampler_itr = find_if(begin(caller.arguments), end(caller.arguments),
|
||||
[sampler_id](const SPIRFunction::Parameter &p) { return p.id == sampler_id; });
|
||||
|
||||
if (texture_itr != end(caller.arguments))
|
||||
{
|
||||
param.global_image = false;
|
||||
param.image_id = texture_itr - begin(caller.arguments);
|
||||
}
|
||||
|
||||
if (sampler_itr != end(caller.arguments))
|
||||
{
|
||||
param.global_sampler = false;
|
||||
param.sampler_id = sampler_itr - begin(caller.arguments);
|
||||
}
|
||||
|
||||
if (param.global_image && param.global_sampler)
|
||||
return;
|
||||
|
||||
auto itr = find_if(begin(caller.combined_parameters), end(caller.combined_parameters),
|
||||
[¶m](const SPIRFunction::CombinedImageSamplerParameter &p) {
|
||||
return param.image_id == p.image_id && param.sampler_id == p.sampler_id &&
|
||||
param.global_image == p.global_image && param.global_sampler == p.global_sampler;
|
||||
});
|
||||
|
||||
if (itr == end(caller.combined_parameters))
|
||||
{
|
||||
uint32_t id = compiler.increase_bound_by(3);
|
||||
auto type_id = id + 0;
|
||||
auto ptr_type_id = id + 1;
|
||||
auto combined_id = id + 2;
|
||||
auto &base = compiler.expression_type(image_id);
|
||||
auto &type = compiler.set<SPIRType>(type_id);
|
||||
auto &ptr_type = compiler.set<SPIRType>(ptr_type_id);
|
||||
|
||||
type = base;
|
||||
type.self = type_id;
|
||||
type.basetype = SPIRType::SampledImage;
|
||||
type.pointer = false;
|
||||
type.storage = StorageClassGeneric;
|
||||
|
||||
ptr_type = type;
|
||||
ptr_type.pointer = true;
|
||||
ptr_type.storage = StorageClassUniformConstant;
|
||||
|
||||
// Build new variable.
|
||||
compiler.set<SPIRVariable>(combined_id, ptr_type_id, StorageClassFunction, 0);
|
||||
|
||||
// Inherit RelaxedPrecision (and potentially other useful flags if deemed relevant).
|
||||
auto &new_flags = compiler.meta[combined_id].decoration.decoration_flags;
|
||||
auto old_flags = compiler.meta[sampler_id].decoration.decoration_flags;
|
||||
new_flags = old_flags & (1ull << DecorationRelaxedPrecision);
|
||||
|
||||
param.id = combined_id;
|
||||
|
||||
compiler.set_name(combined_id,
|
||||
join("SPIRV_Cross_Combined", compiler.to_name(image_id), compiler.to_name(sampler_id)));
|
||||
|
||||
caller.combined_parameters.push_back(param);
|
||||
caller.shadow_arguments.push_back({ ptr_type_id, combined_id, 0u, 0u });
|
||||
}
|
||||
}
|
||||
|
||||
bool Compiler::CombinedImageSamplerHandler::handle(Op opcode, const uint32_t *args, uint32_t length)
|
||||
{
|
||||
// We need to figure out where samplers and images are loaded from, so do only the bare bones compilation we need.
|
||||
switch (opcode)
|
||||
{
|
||||
case OpLoad:
|
||||
{
|
||||
if (length < 3)
|
||||
return false;
|
||||
|
||||
uint32_t result_type = args[0];
|
||||
|
||||
auto &type = compiler.get<SPIRType>(result_type);
|
||||
bool separate_image = type.basetype == SPIRType::Image && type.image.sampled == 1;
|
||||
bool separate_sampler = type.basetype == SPIRType::Sampler;
|
||||
|
||||
// If not separate image or sampler, don't bother.
|
||||
if (!separate_image && !separate_sampler)
|
||||
return true;
|
||||
|
||||
uint32_t id = args[1];
|
||||
uint32_t ptr = args[2];
|
||||
compiler.set<SPIRExpression>(id, "", result_type, true);
|
||||
compiler.register_read(id, ptr, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
case OpInBoundsAccessChain:
|
||||
case OpAccessChain:
|
||||
{
|
||||
if (length < 3)
|
||||
return false;
|
||||
|
||||
// Technically, it is possible to have arrays of textures and arrays of samplers and combine them, but this becomes essentially
|
||||
// impossible to implement, since we don't know which concrete sampler we are accessing.
|
||||
// One potential way is to create a combinatorial explosion where N textures and M samplers are combined into N * M sampler2Ds,
|
||||
// but this seems ridiculously complicated for a problem which is easy to work around.
|
||||
// Checking access chains like this assumes we don't have samplers or textures inside uniform structs, but this makes no sense.
|
||||
|
||||
auto &type = compiler.get<SPIRType>(args[0]);
|
||||
bool separate_image = type.basetype == SPIRType::Image && type.image.sampled == 1;
|
||||
bool separate_sampler = type.basetype == SPIRType::Sampler;
|
||||
if (separate_image)
|
||||
throw CompilerError(
|
||||
"Attempting to use arrays of separate images. This is not possible to statically remap to plain GLSL.");
|
||||
if (separate_sampler)
|
||||
throw CompilerError("Attempting to use arrays of separate samplers. This is not possible to statically "
|
||||
"remap to plain GLSL.");
|
||||
return true;
|
||||
}
|
||||
|
||||
case OpSampledImage:
|
||||
// Do it outside.
|
||||
break;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
if (length < 4)
|
||||
return false;
|
||||
|
||||
// Registers sampler2D calls used in case they are parameters so
|
||||
// that their callees know which combined image samplers to propagate down the call stack.
|
||||
if (!functions.empty())
|
||||
{
|
||||
auto &callee = *functions.top();
|
||||
if (callee.do_combined_parameters)
|
||||
{
|
||||
uint32_t image_id = args[2];
|
||||
|
||||
auto *image = compiler.maybe_get_backing_variable(image_id);
|
||||
if (image)
|
||||
image_id = image->self;
|
||||
|
||||
uint32_t sampler_id = args[3];
|
||||
auto *sampler = compiler.maybe_get_backing_variable(sampler_id);
|
||||
if (sampler)
|
||||
sampler_id = sampler->self;
|
||||
|
||||
register_combined_image_sampler(callee, image_id, sampler_id);
|
||||
}
|
||||
}
|
||||
|
||||
// For function calls, we need to remap IDs which are function parameters into global variables.
|
||||
// This information is statically known from the current place in the call stack.
|
||||
// Function parameters are not necessarily pointers, so if we don't have a backing variable, remapping will know
|
||||
// which backing variable the image/sample came from.
|
||||
uint32_t image_id = remap_parameter(args[2]);
|
||||
uint32_t sampler_id = remap_parameter(args[3]);
|
||||
|
||||
auto itr = find_if(begin(compiler.combined_image_samplers), end(compiler.combined_image_samplers),
|
||||
[image_id, sampler_id](const CombinedImageSampler &combined) {
|
||||
return combined.image_id == image_id && combined.sampler_id == sampler_id;
|
||||
});
|
||||
|
||||
if (itr == end(compiler.combined_image_samplers))
|
||||
{
|
||||
auto id = compiler.increase_bound_by(2);
|
||||
auto type_id = id + 0;
|
||||
auto combined_id = id + 1;
|
||||
auto sampled_type = args[0];
|
||||
|
||||
// Make a new type, pointer to OpTypeSampledImage, so we can make a variable of this type.
|
||||
// We will probably have this type lying around, but it doesn't hurt to make duplicates for internal purposes.
|
||||
auto &type = compiler.set<SPIRType>(type_id);
|
||||
auto &base = compiler.get<SPIRType>(sampled_type);
|
||||
type = base;
|
||||
type.pointer = true;
|
||||
type.storage = StorageClassUniformConstant;
|
||||
|
||||
// Build new variable.
|
||||
compiler.set<SPIRVariable>(combined_id, type_id, StorageClassUniformConstant, 0);
|
||||
|
||||
// Inherit RelaxedPrecision (and potentially other useful flags if deemed relevant).
|
||||
auto &new_flags = compiler.meta[combined_id].decoration.decoration_flags;
|
||||
auto old_flags = compiler.meta[sampler_id].decoration.decoration_flags;
|
||||
new_flags = old_flags & (1ull << DecorationRelaxedPrecision);
|
||||
|
||||
compiler.combined_image_samplers.push_back({ combined_id, image_id, sampler_id });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Compiler::build_combined_image_samplers()
|
||||
{
|
||||
for (auto &id : ids)
|
||||
{
|
||||
if (id.get_type() == TypeFunction)
|
||||
{
|
||||
auto &func = id.get<SPIRFunction>();
|
||||
func.combined_parameters.clear();
|
||||
func.shadow_arguments.clear();
|
||||
func.do_combined_parameters = true;
|
||||
}
|
||||
}
|
||||
|
||||
combined_image_samplers.clear();
|
||||
CombinedImageSamplerHandler handler(*this);
|
||||
traverse_all_reachable_opcodes(get<SPIRFunction>(entry_point), handler);
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include "spirv.hpp"
|
||||
#include <memory>
|
||||
#include <stack>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@ -80,6 +81,16 @@ struct ShaderResources
|
||||
std::vector<Resource> separate_samplers;
|
||||
};
|
||||
|
||||
struct CombinedImageSampler
|
||||
{
|
||||
// The ID of the sampler2D variable.
|
||||
uint32_t combined_id;
|
||||
// The ID of the texture2D variable.
|
||||
uint32_t image_id;
|
||||
// The ID of the sampler variable.
|
||||
uint32_t sampler_id;
|
||||
};
|
||||
|
||||
struct BufferRange
|
||||
{
|
||||
unsigned index;
|
||||
@ -239,6 +250,30 @@ public:
|
||||
uint32_t get_execution_mode_argument(spv::ExecutionMode mode, uint32_t index = 0) const;
|
||||
spv::ExecutionModel get_execution_model() const;
|
||||
|
||||
// Analyzes all separate image and samplers used from the currently selected entry point,
|
||||
// and re-routes them all to a combined image sampler instead.
|
||||
// This is required to "support" separate image samplers in targets which do not natively support
|
||||
// this feature, like GLSL/ESSL.
|
||||
//
|
||||
// This must be called before compile() if such remapping is desired.
|
||||
// This call will add new sampled images to the SPIR-V,
|
||||
// so it will appear in reflection if get_shader_resources() is called after build_combined_image_samplers.
|
||||
//
|
||||
// If any image/sampler remapping was found, no separate image/samplers will appear in the decompiled output,
|
||||
// but will still appear in reflection.
|
||||
//
|
||||
// The resulting samplers will be void of any decorations like name, descriptor sets and binding points,
|
||||
// so this can be added before compile() if desired.
|
||||
//
|
||||
// Combined image samplers originating from this set are always considered active variables.
|
||||
void build_combined_image_samplers();
|
||||
|
||||
// Gets a remapping for the combined image samplers.
|
||||
const std::vector<CombinedImageSampler> &get_combined_image_samplers() const
|
||||
{
|
||||
return combined_image_samplers;
|
||||
}
|
||||
|
||||
protected:
|
||||
const uint32_t *stream(const Instruction &instr) const
|
||||
{
|
||||
@ -395,6 +430,8 @@ protected:
|
||||
// variable is part of that entry points interface.
|
||||
bool interface_variable_exists_in_entry_point(uint32_t id) const;
|
||||
|
||||
std::vector<CombinedImageSampler> combined_image_samplers;
|
||||
|
||||
private:
|
||||
void parse();
|
||||
void parse(const Instruction &i);
|
||||
@ -407,6 +444,16 @@ private:
|
||||
// Return true if traversal should continue.
|
||||
// If false, traversal will end immediately.
|
||||
virtual bool handle(spv::Op opcode, const uint32_t *args, uint32_t length) = 0;
|
||||
|
||||
virtual bool begin_function_scope(const uint32_t *, uint32_t)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool end_function_scope(const uint32_t *, uint32_t)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct BufferAccessHandler : OpcodeHandler
|
||||
@ -441,6 +488,28 @@ private:
|
||||
std::unordered_set<uint32_t> &variables;
|
||||
};
|
||||
|
||||
struct CombinedImageSamplerHandler : OpcodeHandler
|
||||
{
|
||||
CombinedImageSamplerHandler(Compiler &compiler_)
|
||||
: compiler(compiler_)
|
||||
{
|
||||
}
|
||||
bool handle(spv::Op opcode, const uint32_t *args, uint32_t length) override;
|
||||
bool begin_function_scope(const uint32_t *args, uint32_t length) override;
|
||||
bool end_function_scope(const uint32_t *args, uint32_t length) override;
|
||||
|
||||
Compiler &compiler;
|
||||
|
||||
// Each function in the call stack needs its own remapping for parameters so we can deduce which global variable each texture/sampler the parameter is statically bound to.
|
||||
std::stack<std::unordered_map<uint32_t, uint32_t>> parameter_remapping;
|
||||
std::stack<SPIRFunction *> functions;
|
||||
|
||||
uint32_t remap_parameter(uint32_t id);
|
||||
void push_remap_parameters(const SPIRFunction &func, const uint32_t *args, uint32_t length);
|
||||
void pop_remap_parameters();
|
||||
void register_combined_image_sampler(SPIRFunction &caller, uint32_t texture_id, uint32_t sampler_id);
|
||||
};
|
||||
|
||||
bool traverse_all_reachable_opcodes(const SPIRBlock &block, OpcodeHandler &handler) const;
|
||||
bool traverse_all_reachable_opcodes(const SPIRFunction &block, OpcodeHandler &handler) const;
|
||||
// This must be an ordered data structure so we always pick the same type aliases.
|
||||
|
157
spirv_glsl.cpp
157
spirv_glsl.cpp
@ -1190,8 +1190,8 @@ void CompilerGLSL::emit_resources()
|
||||
{
|
||||
auto &var = id.get<SPIRVariable>();
|
||||
auto &type = get<SPIRType>(var.basetype);
|
||||
if (!is_hidden_variable(var) && var.storage != StorageClassFunction && type.pointer &&
|
||||
type.storage == StorageClassPushConstant)
|
||||
if (var.storage != StorageClassFunction && type.pointer && type.storage == StorageClassPushConstant &&
|
||||
!is_hidden_variable(var))
|
||||
{
|
||||
emit_push_constant_block(var);
|
||||
}
|
||||
@ -1200,6 +1200,8 @@ void CompilerGLSL::emit_resources()
|
||||
|
||||
bool emitted = false;
|
||||
|
||||
bool skip_separate_image_sampler = !combined_image_samplers.empty() || !options.vulkan_semantics;
|
||||
|
||||
// Output Uniform Constants (values, samplers, images, etc).
|
||||
for (auto &id : ids)
|
||||
{
|
||||
@ -1208,8 +1210,18 @@ void CompilerGLSL::emit_resources()
|
||||
auto &var = id.get<SPIRVariable>();
|
||||
auto &type = get<SPIRType>(var.basetype);
|
||||
|
||||
if (var.storage != StorageClassFunction && !is_hidden_variable(var) && type.pointer &&
|
||||
(type.storage == StorageClassUniformConstant || type.storage == StorageClassAtomicCounter))
|
||||
// If we're remapping separate samplers and images, only emit the combined samplers.
|
||||
if (skip_separate_image_sampler)
|
||||
{
|
||||
bool separate_image = type.basetype == SPIRType::Image && type.image.sampled == 1;
|
||||
bool separate_sampler = type.basetype == SPIRType::Sampler;
|
||||
if (separate_image || separate_sampler)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (var.storage != StorageClassFunction && type.pointer &&
|
||||
(type.storage == StorageClassUniformConstant || type.storage == StorageClassAtomicCounter) &&
|
||||
!is_hidden_variable(var))
|
||||
{
|
||||
emit_uniform(var);
|
||||
emitted = true;
|
||||
@ -1229,9 +1241,9 @@ void CompilerGLSL::emit_resources()
|
||||
auto &var = id.get<SPIRVariable>();
|
||||
auto &type = get<SPIRType>(var.basetype);
|
||||
|
||||
if (var.storage != StorageClassFunction && !is_hidden_variable(var) && type.pointer &&
|
||||
if (var.storage != StorageClassFunction && type.pointer &&
|
||||
(var.storage == StorageClassInput || var.storage == StorageClassOutput) &&
|
||||
interface_variable_exists_in_entry_point(var.self))
|
||||
interface_variable_exists_in_entry_point(var.self) && !is_hidden_variable(var))
|
||||
{
|
||||
emit_interface_block(var);
|
||||
emitted = true;
|
||||
@ -1864,9 +1876,75 @@ void CompilerGLSL::emit_mix_op(uint32_t result_type, uint32_t id, uint32_t left,
|
||||
emit_trinary_func_op(result_type, id, left, right, lerp, "mix");
|
||||
}
|
||||
|
||||
string CompilerGLSL::to_combined_image_sampler(uint32_t image_id, uint32_t samp_id)
|
||||
{
|
||||
auto &args = current_function->arguments;
|
||||
|
||||
// For GLSL and ESSL targets, we must enumerate all possible combinations for sampler2D(texture2D, sampler) and redirect
|
||||
// all possible combinations into new sampler2D uniforms.
|
||||
auto *image = maybe_get_backing_variable(image_id);
|
||||
auto *samp = maybe_get_backing_variable(samp_id);
|
||||
if (image)
|
||||
image_id = image->self;
|
||||
if (samp)
|
||||
samp_id = samp->self;
|
||||
|
||||
auto image_itr = find_if(begin(args), end(args),
|
||||
[image_id](const SPIRFunction::Parameter ¶m) { return param.id == image_id; });
|
||||
|
||||
auto sampler_itr = find_if(begin(args), end(args),
|
||||
[samp_id](const SPIRFunction::Parameter ¶m) { return param.id == samp_id; });
|
||||
|
||||
if (image_itr != end(args) || sampler_itr != end(args))
|
||||
{
|
||||
// If any parameter originates from a parameter, we will find it in our argument list.
|
||||
bool global_image = image_itr == end(args);
|
||||
bool global_sampler = sampler_itr == end(args);
|
||||
uint32_t iid = global_image ? image_id : (image_itr - begin(args));
|
||||
uint32_t sid = global_sampler ? samp_id : (sampler_itr - begin(args));
|
||||
|
||||
auto &combined = current_function->combined_parameters;
|
||||
auto itr = find_if(begin(combined), end(combined), [=](const SPIRFunction::CombinedImageSamplerParameter &p) {
|
||||
return p.global_image == global_image && p.global_sampler == global_sampler && p.image_id == iid &&
|
||||
p.sampler_id == sid;
|
||||
});
|
||||
|
||||
if (itr != end(combined))
|
||||
return to_expression(itr->id);
|
||||
else
|
||||
{
|
||||
throw CompilerError(
|
||||
"Cannot find mapping for combined sampler parameter, was build_combined_image_samplers() used "
|
||||
"before compile() was called?");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For global sampler2D, look directly at the global remapping table.
|
||||
auto &mapping = combined_image_samplers;
|
||||
auto itr = find_if(begin(mapping), end(mapping), [image_id, samp_id](const CombinedImageSampler &combined) {
|
||||
return combined.image_id == image_id && combined.sampler_id == samp_id;
|
||||
});
|
||||
|
||||
if (itr != end(combined_image_samplers))
|
||||
return to_expression(itr->combined_id);
|
||||
else
|
||||
{
|
||||
throw CompilerError("Cannot find mapping for combined sampler, was build_combined_image_samplers() used "
|
||||
"before compile() was called?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CompilerGLSL::emit_sampled_image_op(uint32_t result_type, uint32_t result_id, uint32_t image_id, uint32_t samp_id)
|
||||
{
|
||||
emit_binary_func_op(result_type, result_id, image_id, samp_id, type_to_glsl(get<SPIRType>(result_type)).c_str());
|
||||
if (options.vulkan_semantics && combined_image_samplers.empty())
|
||||
{
|
||||
emit_binary_func_op(result_type, result_id, image_id, samp_id,
|
||||
type_to_glsl(get<SPIRType>(result_type)).c_str());
|
||||
}
|
||||
else
|
||||
emit_op(result_type, result_id, to_combined_image_sampler(image_id, samp_id), true, false);
|
||||
}
|
||||
|
||||
void CompilerGLSL::emit_texture_op(const Instruction &i)
|
||||
@ -2879,6 +2957,17 @@ string CompilerGLSL::build_composite_combiner(const uint32_t *elems, uint32_t le
|
||||
return op;
|
||||
}
|
||||
|
||||
bool CompilerGLSL::skip_argument(uint32_t id) const
|
||||
{
|
||||
if (!combined_image_samplers.empty() || !options.vulkan_semantics)
|
||||
{
|
||||
auto &type = expression_type(id);
|
||||
if (type.basetype == SPIRType::Sampler || (type.basetype == SPIRType::Image && type.image.sampled == 1))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CompilerGLSL::emit_instruction(const Instruction &instruction)
|
||||
{
|
||||
auto ops = stream(instruction);
|
||||
@ -2993,13 +3082,34 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction)
|
||||
register_impure_function_call();
|
||||
|
||||
string funexpr;
|
||||
vector<string> arglist;
|
||||
funexpr += to_name(func) + "(";
|
||||
for (uint32_t i = 0; i < length; i++)
|
||||
{
|
||||
funexpr += to_expression(arg[i]);
|
||||
if (i + 1 < length)
|
||||
funexpr += ", ";
|
||||
// Do not pass in separate images or samplers if we're remapping
|
||||
// to combined image samplers.
|
||||
if (skip_argument(arg[i]))
|
||||
continue;
|
||||
|
||||
arglist.push_back(to_expression(arg[i]));
|
||||
}
|
||||
|
||||
for (auto &combined : callee.combined_parameters)
|
||||
{
|
||||
uint32_t image_id = combined.global_image ? combined.image_id : arg[combined.image_id];
|
||||
uint32_t sampler_id = combined.global_sampler ? combined.sampler_id : arg[combined.sampler_id];
|
||||
|
||||
auto *image = maybe_get_backing_variable(image_id);
|
||||
if (image)
|
||||
image_id = image->self;
|
||||
|
||||
auto *samp = maybe_get_backing_variable(sampler_id);
|
||||
if (samp)
|
||||
sampler_id = samp->self;
|
||||
|
||||
arglist.push_back(to_combined_image_sampler(image_id, sampler_id));
|
||||
}
|
||||
funexpr += merge(arglist);
|
||||
funexpr += ")";
|
||||
|
||||
// Check for function call constraints.
|
||||
@ -4584,17 +4694,21 @@ void CompilerGLSL::emit_function_prototype(SPIRFunction &func, uint64_t return_f
|
||||
decl += to_name(func.self);
|
||||
|
||||
decl += "(";
|
||||
vector<string> arglist;
|
||||
for (auto &arg : func.arguments)
|
||||
{
|
||||
// Do not pass in separate images or samplers if we're remapping
|
||||
// to combined image samplers.
|
||||
if (skip_argument(arg.id))
|
||||
continue;
|
||||
|
||||
// Might change the variable name if it already exists in this function.
|
||||
// SPIRV OpName doesn't have any semantic effect, so it's valid for an implementation
|
||||
// to use same name for variables.
|
||||
// Since we want to make the GLSL debuggable and somewhat sane, use fallback names for variables which are duplicates.
|
||||
add_local_variable_name(arg.id);
|
||||
|
||||
decl += argument_decl(arg);
|
||||
if (&arg != &func.arguments.back())
|
||||
decl += ", ";
|
||||
arglist.push_back(argument_decl(arg));
|
||||
|
||||
// Hold a pointer to the parameter so we can invalidate the readonly field if needed.
|
||||
auto *var = maybe_get<SPIRVariable>(arg.id);
|
||||
@ -4602,6 +4716,23 @@ void CompilerGLSL::emit_function_prototype(SPIRFunction &func, uint64_t return_f
|
||||
var->parameter = &arg;
|
||||
}
|
||||
|
||||
for (auto &arg : func.shadow_arguments)
|
||||
{
|
||||
// Might change the variable name if it already exists in this function.
|
||||
// SPIRV OpName doesn't have any semantic effect, so it's valid for an implementation
|
||||
// to use same name for variables.
|
||||
// Since we want to make the GLSL debuggable and somewhat sane, use fallback names for variables which are duplicates.
|
||||
add_local_variable_name(arg.id);
|
||||
|
||||
arglist.push_back(argument_decl(arg));
|
||||
|
||||
// Hold a pointer to the parameter so we can invalidate the readonly field if needed.
|
||||
auto *var = maybe_get<SPIRVariable>(arg.id);
|
||||
if (var)
|
||||
var->parameter = &arg;
|
||||
}
|
||||
|
||||
decl += merge(arglist);
|
||||
decl += ")";
|
||||
statement(decl);
|
||||
}
|
||||
|
@ -291,6 +291,8 @@ protected:
|
||||
std::string layout_for_member(const SPIRType &type, uint32_t index);
|
||||
uint64_t combined_decoration_for_member(const SPIRType &type, uint32_t index);
|
||||
std::string layout_for_variable(const SPIRVariable &variable);
|
||||
std::string to_combined_image_sampler(uint32_t image_id, uint32_t samp_id);
|
||||
bool skip_argument(uint32_t id) const;
|
||||
|
||||
bool ssbo_is_std430_packing(const SPIRType &type);
|
||||
uint32_t type_to_std430_base_size(const SPIRType &type);
|
||||
|
Loading…
Reference in New Issue
Block a user