Merge pull request #48 from KhronosGroup/combined-image-samplers

Support separate image samplers in GLSL/ESSL
This commit is contained in:
Hans-Kristian Arntzen 2016-09-11 19:18:17 +02:00 committed by GitHub
commit 41f9fd9ef0
13 changed files with 766 additions and 57 deletions

View File

@ -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();

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View 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;
}

View File

@ -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

View File

@ -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 &params = functions.top()->combined_parameters;
functions.pop();
if (functions.empty())
return true;
auto &caller = *functions.top();
if (caller.do_combined_parameters)
{
for (auto &param : 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),
[&param](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);
}

View File

@ -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.

View File

@ -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 &param) { return param.id == image_id; });
auto sampler_itr = find_if(begin(args), end(args),
[samp_id](const SPIRFunction::Parameter &param) { 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);
}

View File

@ -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);