• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The Dawn Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "dawn_native/opengl/ShaderModuleGL.h"
16 
17 #include "common/Assert.h"
18 #include "common/Platform.h"
19 #include "dawn_native/BindGroupLayout.h"
20 #include "dawn_native/SpirvValidation.h"
21 #include "dawn_native/TintUtils.h"
22 #include "dawn_native/opengl/DeviceGL.h"
23 #include "dawn_native/opengl/PipelineLayoutGL.h"
24 #include "dawn_native/opengl/SpirvUtils.h"
25 
26 #include <spirv_glsl.hpp>
27 
28 // Tint include must be after spirv_glsl.hpp, because spirv-cross has its own
29 // version of spirv_headers. We also need to undef SPV_REVISION because SPIRV-Cross
30 // is at 3 while spirv-headers is at 4.
31 #undef SPV_REVISION
32 #include <tint/tint.h>
33 #include <spirv-tools/libspirv.hpp>
34 
35 #include <sstream>
36 
37 namespace dawn_native { namespace opengl {
38 
GetBindingName(BindGroupIndex group,BindingNumber bindingNumber)39     std::string GetBindingName(BindGroupIndex group, BindingNumber bindingNumber) {
40         std::ostringstream o;
41         o << "dawn_binding_" << static_cast<uint32_t>(group) << "_"
42           << static_cast<uint32_t>(bindingNumber);
43         return o.str();
44     }
45 
operator <(const BindingLocation & a,const BindingLocation & b)46     bool operator<(const BindingLocation& a, const BindingLocation& b) {
47         return std::tie(a.group, a.binding) < std::tie(b.group, b.binding);
48     }
49 
operator <(const CombinedSampler & a,const CombinedSampler & b)50     bool operator<(const CombinedSampler& a, const CombinedSampler& b) {
51         return std::tie(a.useDummySampler, a.samplerLocation, a.textureLocation) <
52                std::tie(b.useDummySampler, a.samplerLocation, b.textureLocation);
53     }
54 
GetName() const55     std::string CombinedSampler::GetName() const {
56         std::ostringstream o;
57         o << "dawn_combined";
58         if (useDummySampler) {
59             o << "_dummy_sampler";
60         } else {
61             o << "_" << static_cast<uint32_t>(samplerLocation.group) << "_"
62               << static_cast<uint32_t>(samplerLocation.binding);
63         }
64         o << "_with_" << static_cast<uint32_t>(textureLocation.group) << "_"
65           << static_cast<uint32_t>(textureLocation.binding);
66         return o.str();
67     }
68 
ExtractSpirvInfo(const DeviceBase * device,const spirv_cross::Compiler & compiler,const std::string & entryPointName,SingleShaderStage stage)69     ResultOrError<std::unique_ptr<BindingInfoArray>> ExtractSpirvInfo(
70         const DeviceBase* device,
71         const spirv_cross::Compiler& compiler,
72         const std::string& entryPointName,
73         SingleShaderStage stage) {
74         const auto& resources = compiler.get_shader_resources();
75 
76         // Fill in bindingInfo with the SPIRV bindings
77         auto ExtractResourcesBinding =
78             [](const DeviceBase* device,
79                const spirv_cross::SmallVector<spirv_cross::Resource>& resources,
80                const spirv_cross::Compiler& compiler, BindingInfoType bindingType,
81                BindingInfoArray* bindings, bool isStorageBuffer = false) -> MaybeError {
82             for (const auto& resource : resources) {
83                 DAWN_INVALID_IF(
84                     !compiler.get_decoration_bitset(resource.id).get(spv::DecorationBinding),
85                     "No Binding decoration set for resource");
86 
87                 DAWN_INVALID_IF(
88                     !compiler.get_decoration_bitset(resource.id).get(spv::DecorationDescriptorSet),
89                     "No Descriptor Decoration set for resource");
90 
91                 BindingNumber bindingNumber(
92                     compiler.get_decoration(resource.id, spv::DecorationBinding));
93                 BindGroupIndex bindGroupIndex(
94                     compiler.get_decoration(resource.id, spv::DecorationDescriptorSet));
95 
96                 DAWN_INVALID_IF(bindGroupIndex >= kMaxBindGroupsTyped,
97                                 "Bind group index over limits in the SPIRV");
98 
99                 const auto& it =
100                     (*bindings)[bindGroupIndex].emplace(bindingNumber, ShaderBindingInfo{});
101                 DAWN_INVALID_IF(!it.second, "Shader has duplicate bindings");
102 
103                 ShaderBindingInfo* info = &it.first->second;
104                 info->id = resource.id;
105                 info->base_type_id = resource.base_type_id;
106                 info->bindingType = bindingType;
107 
108                 switch (bindingType) {
109                     case BindingInfoType::Texture: {
110                         spirv_cross::SPIRType::ImageType imageType =
111                             compiler.get_type(info->base_type_id).image;
112                         spirv_cross::SPIRType::BaseType textureComponentType =
113                             compiler.get_type(imageType.type).basetype;
114 
115                         info->texture.viewDimension =
116                             SpirvDimToTextureViewDimension(imageType.dim, imageType.arrayed);
117                         info->texture.multisampled = imageType.ms;
118                         info->texture.compatibleSampleTypes =
119                             SpirvBaseTypeToSampleTypeBit(textureComponentType);
120 
121                         if (imageType.depth) {
122                             DAWN_INVALID_IF(
123                                 (info->texture.compatibleSampleTypes & SampleTypeBit::Float) == 0,
124                                 "Depth textures must have a float type");
125                             info->texture.compatibleSampleTypes = SampleTypeBit::Depth;
126                         }
127 
128                         DAWN_INVALID_IF(imageType.ms && imageType.arrayed,
129                                         "Multisampled array textures aren't supported");
130                         break;
131                     }
132                     case BindingInfoType::Buffer: {
133                         // Determine buffer size, with a minimum of 1 element in the runtime
134                         // array
135                         spirv_cross::SPIRType type = compiler.get_type(info->base_type_id);
136                         info->buffer.minBindingSize =
137                             compiler.get_declared_struct_size_runtime_array(type, 1);
138 
139                         // Differentiate between readonly storage bindings and writable ones
140                         // based on the NonWritable decoration.
141                         // TODO(dawn:527): Could isStorageBuffer be determined by calling
142                         // compiler.get_storage_class(resource.id)?
143                         if (isStorageBuffer) {
144                             spirv_cross::Bitset flags =
145                                 compiler.get_buffer_block_flags(resource.id);
146                             if (flags.get(spv::DecorationNonWritable)) {
147                                 info->buffer.type = wgpu::BufferBindingType::ReadOnlyStorage;
148                             } else {
149                                 info->buffer.type = wgpu::BufferBindingType::Storage;
150                             }
151                         } else {
152                             info->buffer.type = wgpu::BufferBindingType::Uniform;
153                         }
154                         break;
155                     }
156                     case BindingInfoType::StorageTexture: {
157                         spirv_cross::Bitset flags = compiler.get_decoration_bitset(resource.id);
158                         DAWN_INVALID_IF(!flags.get(spv::DecorationNonReadable),
159                                         "Read-write storage textures are not supported.");
160                         info->storageTexture.access = wgpu::StorageTextureAccess::WriteOnly;
161 
162                         spirv_cross::SPIRType::ImageType imageType =
163                             compiler.get_type(info->base_type_id).image;
164                         wgpu::TextureFormat storageTextureFormat =
165                             SpirvImageFormatToTextureFormat(imageType.format);
166                         DAWN_INVALID_IF(storageTextureFormat == wgpu::TextureFormat::Undefined,
167                                         "Invalid image format declaration on storage image.");
168 
169                         const Format& format = device->GetValidInternalFormat(storageTextureFormat);
170                         DAWN_INVALID_IF(!format.supportsStorageUsage,
171                                         "The storage texture format (%s) is not supported.",
172                                         storageTextureFormat);
173 
174                         DAWN_INVALID_IF(imageType.ms,
175                                         "Multisampled storage textures aren't supported.");
176 
177                         DAWN_INVALID_IF(imageType.depth,
178                                         "Depth storage textures aren't supported.");
179 
180                         info->storageTexture.format = storageTextureFormat;
181                         info->storageTexture.viewDimension =
182                             SpirvDimToTextureViewDimension(imageType.dim, imageType.arrayed);
183                         break;
184                     }
185                     case BindingInfoType::Sampler: {
186                         info->sampler.isComparison = false;
187                         break;
188                     }
189                     case BindingInfoType::ExternalTexture: {
190                         return DAWN_FORMAT_VALIDATION_ERROR("External textures are not supported.");
191                     }
192                 }
193             }
194             return {};
195         };
196 
197         std::unique_ptr<BindingInfoArray> resultBindings = std::make_unique<BindingInfoArray>();
198         BindingInfoArray* bindings = resultBindings.get();
199         DAWN_TRY(ExtractResourcesBinding(device, resources.uniform_buffers, compiler,
200                                          BindingInfoType::Buffer, bindings));
201         DAWN_TRY(ExtractResourcesBinding(device, resources.separate_images, compiler,
202                                          BindingInfoType::Texture, bindings));
203         DAWN_TRY(ExtractResourcesBinding(device, resources.separate_samplers, compiler,
204                                          BindingInfoType::Sampler, bindings));
205         DAWN_TRY(ExtractResourcesBinding(device, resources.storage_buffers, compiler,
206                                          BindingInfoType::Buffer, bindings, true));
207         // ReadonlyStorageTexture is used as a tag to do general storage texture handling.
208         DAWN_TRY(ExtractResourcesBinding(device, resources.storage_images, compiler,
209                                          BindingInfoType::StorageTexture, resultBindings.get()));
210 
211         return {std::move(resultBindings)};
212     }
213 
214     // static
Create(Device * device,const ShaderModuleDescriptor * descriptor,ShaderModuleParseResult * parseResult)215     ResultOrError<Ref<ShaderModule>> ShaderModule::Create(Device* device,
216                                                           const ShaderModuleDescriptor* descriptor,
217                                                           ShaderModuleParseResult* parseResult) {
218         Ref<ShaderModule> module = AcquireRef(new ShaderModule(device, descriptor));
219         DAWN_TRY(module->Initialize(parseResult));
220         return module;
221     }
222 
ShaderModule(Device * device,const ShaderModuleDescriptor * descriptor)223     ShaderModule::ShaderModule(Device* device, const ShaderModuleDescriptor* descriptor)
224         : ShaderModuleBase(device, descriptor) {
225     }
226 
227     // static
ReflectShaderUsingSPIRVCross(DeviceBase * device,const std::vector<uint32_t> & spirv)228     ResultOrError<BindingInfoArrayTable> ShaderModule::ReflectShaderUsingSPIRVCross(
229         DeviceBase* device,
230         const std::vector<uint32_t>& spirv) {
231         BindingInfoArrayTable result;
232         spirv_cross::Compiler compiler(spirv);
233         for (const spirv_cross::EntryPoint& entryPoint : compiler.get_entry_points_and_stages()) {
234             ASSERT(result.count(entryPoint.name) == 0);
235 
236             SingleShaderStage stage = ExecutionModelToShaderStage(entryPoint.execution_model);
237             compiler.set_entry_point(entryPoint.name, entryPoint.execution_model);
238 
239             std::unique_ptr<BindingInfoArray> bindings;
240             DAWN_TRY_ASSIGN(bindings, ExtractSpirvInfo(device, compiler, entryPoint.name, stage));
241             result[entryPoint.name] = std::move(bindings);
242         }
243         return std::move(result);
244     }
245 
Initialize(ShaderModuleParseResult * parseResult)246     MaybeError ShaderModule::Initialize(ShaderModuleParseResult* parseResult) {
247         ScopedTintICEHandler scopedICEHandler(GetDevice());
248 
249         DAWN_TRY(InitializeBase(parseResult));
250         // Tint currently does not support emitting GLSL, so when provided a Tint program need to
251         // generate SPIRV and SPIRV-Cross reflection data to be used in this backend.
252         tint::writer::spirv::Options options;
253         options.disable_workgroup_init = GetDevice()->IsToggleEnabled(Toggle::DisableWorkgroupInit);
254         auto result = tint::writer::spirv::Generate(GetTintProgram(), options);
255         DAWN_INVALID_IF(!result.success, "An error occured while generating SPIR-V: %s.",
256                         result.error);
257 
258         DAWN_TRY_ASSIGN(mGLBindings, ReflectShaderUsingSPIRVCross(GetDevice(), result.spirv));
259 
260         return {};
261     }
262 
TranslateToGLSL(const char * entryPointName,SingleShaderStage stage,CombinedSamplerInfo * combinedSamplers,const PipelineLayout * layout,bool * needsDummySampler) const263     ResultOrError<std::string> ShaderModule::TranslateToGLSL(const char* entryPointName,
264                                                              SingleShaderStage stage,
265                                                              CombinedSamplerInfo* combinedSamplers,
266                                                              const PipelineLayout* layout,
267                                                              bool* needsDummySampler) const {
268         tint::transform::SingleEntryPoint singleEntryPointTransform;
269 
270         tint::transform::DataMap transformInputs;
271         transformInputs.Add<tint::transform::SingleEntryPoint::Config>(entryPointName);
272 
273         tint::Program program;
274         DAWN_TRY_ASSIGN(program, RunTransforms(&singleEntryPointTransform, GetTintProgram(),
275                                                transformInputs, nullptr, nullptr));
276 
277         tint::writer::spirv::Options tintOptions;
278         tintOptions.disable_workgroup_init =
279             GetDevice()->IsToggleEnabled(Toggle::DisableWorkgroupInit);
280         auto result = tint::writer::spirv::Generate(&program, tintOptions);
281         DAWN_INVALID_IF(!result.success, "An error occured while generating SPIR-V: %s.",
282                         result.error);
283 
284         std::vector<uint32_t> spirv = std::move(result.spirv);
285         DAWN_TRY(
286             ValidateSpirv(GetDevice(), spirv, GetDevice()->IsToggleEnabled(Toggle::DumpShaders)));
287 
288         // If these options are changed, the values in DawnSPIRVCrossGLSLFastFuzzer.cpp need to
289         // be updated.
290         spirv_cross::CompilerGLSL::Options options;
291 
292         // The range of Z-coordinate in the clipping volume of OpenGL is [-w, w], while it is
293         // [0, w] in D3D12, Metal and Vulkan, so we should normalize it in shaders in all
294         // backends. See the documentation of
295         // spirv_cross::CompilerGLSL::Options::vertex::fixup_clipspace for more details.
296         options.vertex.flip_vert_y = true;
297         options.vertex.fixup_clipspace = true;
298 
299         const OpenGLVersion& version = ToBackend(GetDevice())->gl.GetVersion();
300         if (version.IsDesktop()) {
301             // The computation of GLSL version below only works for 3.3 and above.
302             ASSERT(version.IsAtLeast(3, 3));
303         }
304         options.es = version.IsES();
305         options.version = version.GetMajor() * 100 + version.GetMinor() * 10;
306 
307         spirv_cross::CompilerGLSL compiler(std::move(spirv));
308         compiler.set_common_options(options);
309         compiler.set_entry_point(entryPointName, ShaderStageToExecutionModel(stage));
310 
311         // Analyzes all OpImageFetch opcodes and checks if there are instances where
312         // said instruction is used without a combined image sampler.
313         // GLSL does not support texelFetch without a sampler.
314         // To workaround this, we must inject a dummy sampler which can be used to form a sampler2D
315         // at the call-site of texelFetch as necessary.
316         spirv_cross::VariableID dummySamplerId = compiler.build_dummy_sampler_for_combined_images();
317 
318         // Extract bindings names so that it can be used to get its location in program.
319         // Now translate the separate sampler / textures into combined ones and store their info. We
320         // need to do this before removing the set and binding decorations.
321         compiler.build_combined_image_samplers();
322 
323         for (const auto& combined : compiler.get_combined_image_samplers()) {
324             combinedSamplers->emplace_back();
325 
326             CombinedSampler* info = &combinedSamplers->back();
327             if (combined.sampler_id == dummySamplerId) {
328                 *needsDummySampler = true;
329                 info->useDummySampler = true;
330                 info->samplerLocation = {};
331             } else {
332                 info->useDummySampler = false;
333                 info->samplerLocation.group = BindGroupIndex(
334                     compiler.get_decoration(combined.sampler_id, spv::DecorationDescriptorSet));
335                 info->samplerLocation.binding = BindingNumber(
336                     compiler.get_decoration(combined.sampler_id, spv::DecorationBinding));
337             }
338             info->textureLocation.group = BindGroupIndex(
339                 compiler.get_decoration(combined.image_id, spv::DecorationDescriptorSet));
340             info->textureLocation.binding =
341                 BindingNumber(compiler.get_decoration(combined.image_id, spv::DecorationBinding));
342             compiler.set_name(combined.combined_id, info->GetName());
343         }
344 
345         const BindingInfoArray& bindingInfo = *(mGLBindings.at(entryPointName));
346 
347         // Change binding names to be "dawn_binding_<group>_<binding>".
348         // Also unsets the SPIRV "Binding" decoration as it outputs "layout(binding=)" which
349         // isn't supported on OSX's OpenGL.
350         const PipelineLayout::BindingIndexInfo& indices = layout->GetBindingIndexInfo();
351 
352         // Modify the decoration of variables so that SPIRV-Cross outputs only
353         //  layout(binding=<index>) for interface variables.
354         //
355         // Tint is used for the reflection of bindings for the implicit pipeline layout and
356         // pipeline/layout validation, but bindingInfo is set to mGLEntryPoints which is the
357         // SPIRV-Cross reflection. Tint reflects bindings used more precisely than SPIRV-Cross so
358         // some bindings in bindingInfo might not exist in the layout and querying the layout for
359         // them would cause an ASSERT. That's why we defensively check that bindings are in the
360         // layout before modifying them. This slight hack is ok because in the long term we will use
361         // Tint to produce GLSL.
362         for (BindGroupIndex group : IterateBitSet(layout->GetBindGroupLayoutsMask())) {
363             for (const auto& it : bindingInfo[group]) {
364                 const BindGroupLayoutBase* bgl = layout->GetBindGroupLayout(group);
365                 BindingNumber bindingNumber = it.first;
366                 const auto& info = it.second;
367 
368                 if (!bgl->HasBinding(bindingNumber)) {
369                     continue;
370                 }
371 
372                 // Remove the name of the base type. This works around an issue where if the SPIRV
373                 // has two uniform/storage interface variables that point to the same base type,
374                 // then SPIRV-Cross would emit two bindings with type names that conflict:
375                 //
376                 //   layout(binding=0) uniform Buf {...} binding0;
377                 //   layout(binding=1) uniform Buf {...} binding1;
378                 compiler.set_name(info.base_type_id, "");
379 
380                 BindingIndex bindingIndex = bgl->GetBindingIndex(bindingNumber);
381 
382                 compiler.unset_decoration(info.id, spv::DecorationDescriptorSet);
383                 compiler.set_decoration(info.id, spv::DecorationBinding,
384                                         indices[group][bindingIndex]);
385             }
386         }
387 
388         std::string glsl = compiler.compile();
389 
390         if (GetDevice()->IsToggleEnabled(Toggle::DumpShaders)) {
391             std::ostringstream dumpedMsg;
392             dumpedMsg << "/* Dumped generated GLSL */" << std::endl << glsl;
393 
394             GetDevice()->EmitLog(WGPULoggingType_Info, dumpedMsg.str().c_str());
395         }
396 
397         return glsl;
398     }
399 
400 }}  // namespace dawn_native::opengl
401