OpenGL: Bind a dummy sampler for OpImageFetch if not present
In WGSL, textureLoad translates to an OpImageFetch without the
combined sampler. In OpenGL, we need to use SPIRV-Cross to insert
a dummy nearest filtering sampler and bind that in the backend.
Bug: dawn:585
Change-Id: I92ae6ad35263d3720e59fa93688ca914a9495a81
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/34401
Reviewed-by: Stephen White <senorblanco@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_native/opengl/PipelineGL.cpp b/src/dawn_native/opengl/PipelineGL.cpp
index ee4aa5c..3396c49 100644
--- a/src/dawn_native/opengl/PipelineGL.cpp
+++ b/src/dawn_native/opengl/PipelineGL.cpp
@@ -17,10 +17,12 @@
#include "common/BitSetIterator.h"
#include "common/Log.h"
#include "dawn_native/BindGroupLayout.h"
+#include "dawn_native/Device.h"
#include "dawn_native/Pipeline.h"
#include "dawn_native/opengl/Forward.h"
#include "dawn_native/opengl/OpenGLFunctions.h"
#include "dawn_native/opengl/PipelineLayoutGL.h"
+#include "dawn_native/opengl/SamplerGL.h"
#include "dawn_native/opengl/ShaderModuleGL.h"
#include <set>
@@ -42,8 +44,8 @@
} // namespace
- PipelineGL::PipelineGL() {
- }
+ PipelineGL::PipelineGL() = default;
+ PipelineGL::~PipelineGL() = default;
void PipelineGL::Initialize(const OpenGLFunctions& gl,
const PipelineLayout* layout,
@@ -82,14 +84,25 @@
// Create an OpenGL shader for each stage and gather the list of combined samplers.
PerStage<CombinedSamplerInfo> combinedSamplers;
+ bool needsDummySampler = false;
for (SingleShaderStage stage : IterateStages(activeStages)) {
const ShaderModule* module = ToBackend(stages[stage].module.Get());
- std::string glsl = module->TranslateToGLSL(stages[stage].entryPoint.c_str(), stage,
- &combinedSamplers[stage], layout);
+ std::string glsl =
+ module->TranslateToGLSL(stages[stage].entryPoint.c_str(), stage,
+ &combinedSamplers[stage], layout, &needsDummySampler);
GLuint shader = CreateShader(gl, GLShaderType(stage), glsl.c_str());
gl.AttachShader(mProgram, shader);
}
+ if (needsDummySampler) {
+ SamplerDescriptor desc = {};
+ ASSERT(desc.minFilter == wgpu::FilterMode::Nearest);
+ ASSERT(desc.magFilter == wgpu::FilterMode::Nearest);
+ ASSERT(desc.mipmapFilter == wgpu::FilterMode::Nearest);
+ mDummySampler = AcquireRef(
+ ToBackend(layout->GetDevice()->GetOrCreateSampler(&desc).AcquireSuccess()));
+ }
+
// Link all the shaders together.
gl.LinkProgram(mProgram);
@@ -204,13 +217,18 @@
wgpu::TextureComponentType::Float;
}
{
- const BindGroupLayoutBase* bgl =
- layout->GetBindGroupLayout(combined.samplerLocation.group);
- BindingIndex bindingIndex =
- bgl->GetBindingIndex(combined.samplerLocation.binding);
+ if (combined.useDummySampler) {
+ mDummySamplerUnits.push_back(textureUnit);
+ } else {
+ const BindGroupLayoutBase* bgl =
+ layout->GetBindGroupLayout(combined.samplerLocation.group);
+ BindingIndex bindingIndex =
+ bgl->GetBindingIndex(combined.samplerLocation.binding);
- GLuint samplerIndex = indices[combined.samplerLocation.group][bindingIndex];
- mUnitsForSamplers[samplerIndex].push_back({textureUnit, shouldUseFiltering});
+ GLuint samplerIndex = indices[combined.samplerLocation.group][bindingIndex];
+ mUnitsForSamplers[samplerIndex].push_back(
+ {textureUnit, shouldUseFiltering});
+ }
}
textureUnit++;
@@ -235,6 +253,10 @@
void PipelineGL::ApplyNow(const OpenGLFunctions& gl) {
gl.UseProgram(mProgram);
+ for (GLuint unit : mDummySamplerUnits) {
+ ASSERT(mDummySampler.Get() != nullptr);
+ gl.BindSampler(unit, mDummySampler->GetNonFilteringHandle());
+ }
}
}} // namespace dawn_native::opengl
diff --git a/src/dawn_native/opengl/PipelineGL.h b/src/dawn_native/opengl/PipelineGL.h
index ffef4d2..1d995d0 100644
--- a/src/dawn_native/opengl/PipelineGL.h
+++ b/src/dawn_native/opengl/PipelineGL.h
@@ -31,10 +31,12 @@
struct OpenGLFunctions;
class PersistentPipelineState;
class PipelineLayout;
+ class Sampler;
class PipelineGL {
public:
PipelineGL();
+ ~PipelineGL();
void Initialize(const OpenGLFunctions& gl,
const PipelineLayout* layout,
@@ -56,6 +58,10 @@
GLuint mProgram;
std::vector<std::vector<SamplerUnit>> mUnitsForSamplers;
std::vector<std::vector<GLuint>> mUnitsForTextures;
+ std::vector<GLuint> mDummySamplerUnits;
+ // TODO(enga): This could live on the Device, or elsewhere, but currently it makes Device
+ // destruction complex as it requires the sampler to be destroyed before the sampler cache.
+ Ref<Sampler> mDummySampler;
};
}} // namespace dawn_native::opengl
diff --git a/src/dawn_native/opengl/ShaderModuleGL.cpp b/src/dawn_native/opengl/ShaderModuleGL.cpp
index ff2ecaf..792a2fc 100644
--- a/src/dawn_native/opengl/ShaderModuleGL.cpp
+++ b/src/dawn_native/opengl/ShaderModuleGL.cpp
@@ -39,15 +39,19 @@
}
bool operator<(const CombinedSampler& a, const CombinedSampler& b) {
- return std::tie(a.samplerLocation, a.textureLocation) <
- std::tie(b.samplerLocation, b.textureLocation);
+ return std::tie(a.useDummySampler, a.samplerLocation, a.textureLocation) <
+ std::tie(b.useDummySampler, a.samplerLocation, b.textureLocation);
}
std::string CombinedSampler::GetName() const {
std::ostringstream o;
o << "dawn_combined";
- o << "_" << static_cast<uint32_t>(samplerLocation.group) << "_"
- << static_cast<uint32_t>(samplerLocation.binding);
+ if (useDummySampler) {
+ o << "_dummy_sampler";
+ } else {
+ o << "_" << static_cast<uint32_t>(samplerLocation.group) << "_"
+ << static_cast<uint32_t>(samplerLocation.binding);
+ }
o << "_with_" << static_cast<uint32_t>(textureLocation.group) << "_"
<< static_cast<uint32_t>(textureLocation.binding);
return o.str();
@@ -68,7 +72,8 @@
std::string ShaderModule::TranslateToGLSL(const char* entryPointName,
SingleShaderStage stage,
CombinedSamplerInfo* combinedSamplers,
- const PipelineLayout* layout) const {
+ const PipelineLayout* layout,
+ bool* needsDummySampler) const {
// If these options are changed, the values in DawnSPIRVCrossGLSLFastFuzzer.cpp need to
// be updated.
spirv_cross::CompilerGLSL::Options options;
@@ -92,6 +97,13 @@
compiler.set_common_options(options);
compiler.set_entry_point(entryPointName, ShaderStageToExecutionModel(stage));
+ // Analyzes all OpImageFetch opcodes and checks if there are instances where
+ // said instruction is used without a combined image sampler.
+ // GLSL does not support texelFetch without a sampler.
+ // To workaround this, we must inject a dummy sampler which can be used to form a sampler2D
+ // at the call-site of texelFetch as necessary.
+ spirv_cross::VariableID dummySamplerId = compiler.build_dummy_sampler_for_combined_images();
+
// Extract bindings names so that it can be used to get its location in program.
// Now translate the separate sampler / textures into combined ones and store their info. We
// need to do this before removing the set and binding decorations.
@@ -101,10 +113,17 @@
combinedSamplers->emplace_back();
CombinedSampler* info = &combinedSamplers->back();
- info->samplerLocation.group = BindGroupIndex(
- compiler.get_decoration(combined.sampler_id, spv::DecorationDescriptorSet));
- info->samplerLocation.binding =
- BindingNumber(compiler.get_decoration(combined.sampler_id, spv::DecorationBinding));
+ if (combined.sampler_id == dummySamplerId) {
+ *needsDummySampler = true;
+ info->useDummySampler = true;
+ info->samplerLocation = {};
+ } else {
+ info->useDummySampler = false;
+ info->samplerLocation.group = BindGroupIndex(
+ compiler.get_decoration(combined.sampler_id, spv::DecorationDescriptorSet));
+ info->samplerLocation.binding = BindingNumber(
+ compiler.get_decoration(combined.sampler_id, spv::DecorationBinding));
+ }
info->textureLocation.group = BindGroupIndex(
compiler.get_decoration(combined.image_id, spv::DecorationDescriptorSet));
info->textureLocation.binding =
diff --git a/src/dawn_native/opengl/ShaderModuleGL.h b/src/dawn_native/opengl/ShaderModuleGL.h
index 65456dd..c18fa48 100644
--- a/src/dawn_native/opengl/ShaderModuleGL.h
+++ b/src/dawn_native/opengl/ShaderModuleGL.h
@@ -35,6 +35,9 @@
struct CombinedSampler {
BindingLocation samplerLocation;
BindingLocation textureLocation;
+ // OpenGL requires a sampler with texelFetch. If this is true, the developer did not provide
+ // one and Dawn should bind a dummy non-filtering sampler. |samplerLocation| is unused.
+ bool useDummySampler;
std::string GetName() const;
};
bool operator<(const CombinedSampler& a, const CombinedSampler& b);
@@ -49,7 +52,8 @@
std::string TranslateToGLSL(const char* entryPointName,
SingleShaderStage stage,
CombinedSamplerInfo* combinedSamplers,
- const PipelineLayout* layout) const;
+ const PipelineLayout* layout,
+ bool* needsDummySampler) const;
private:
ShaderModule(Device* device, const ShaderModuleDescriptor* descriptor);