blob: b2d3aef54dc0fb9d84e14d8f5f2128e9bfe98dcd [file] [log] [blame]
// Copyright 2017 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "dawn/native/opengl/ShaderModuleGL.h"
#include <sstream>
#include <unordered_map>
#include <utility>
#include "dawn/native/BindGroupLayoutInternal.h"
#include "dawn/native/CacheRequest.h"
#include "dawn/native/Pipeline.h"
#include "dawn/native/TintUtils.h"
#include "dawn/native/opengl/BindingPoint.h"
#include "dawn/native/opengl/DeviceGL.h"
#include "dawn/native/opengl/PipelineLayoutGL.h"
#include "dawn/platform/DawnPlatform.h"
#include "dawn/platform/tracing/TraceEvent.h"
#include "tint/tint.h"
namespace dawn::native {
namespace {
GLenum GLShaderType(SingleShaderStage stage) {
switch (stage) {
case SingleShaderStage::Vertex:
return GL_VERTEX_SHADER;
case SingleShaderStage::Fragment:
return GL_FRAGMENT_SHADER;
case SingleShaderStage::Compute:
return GL_COMPUTE_SHADER;
}
DAWN_UNREACHABLE();
}
tint::glsl::writer::Version::Standard ToTintGLStandard(opengl::OpenGLVersion::Standard standard) {
switch (standard) {
case opengl::OpenGLVersion::Standard::Desktop:
return tint::glsl::writer::Version::Standard::kDesktop;
case opengl::OpenGLVersion::Standard::ES:
return tint::glsl::writer::Version::Standard::kES;
}
DAWN_UNREACHABLE();
}
using BindingMap = std::unordered_map<tint::BindingPoint, tint::BindingPoint>;
opengl::CombinedSampler* AppendCombinedSampler(opengl::CombinedSamplerInfo* info,
tint::BindingPoint texture,
tint::BindingPoint sampler,
tint::BindingPoint placeholderBindingPoint) {
info->emplace_back();
opengl::CombinedSampler* combinedSampler = &info->back();
combinedSampler->usePlaceholderSampler = sampler == placeholderBindingPoint;
combinedSampler->samplerLocation.group = BindGroupIndex(sampler.group);
combinedSampler->samplerLocation.binding = BindingNumber(sampler.binding);
combinedSampler->textureLocation.group = BindGroupIndex(texture.group);
combinedSampler->textureLocation.binding = BindingNumber(texture.binding);
return combinedSampler;
}
using InterstageLocationAndName = std::pair<uint32_t, std::string>;
#define GLSL_COMPILATION_REQUEST_MEMBERS(X) \
X(const tint::Program*, inputProgram) \
X(std::string, entryPointName) \
X(SingleShaderStage, stage) \
X(std::optional<tint::ast::transform::SubstituteOverride::Config>, substituteOverrideConfig) \
X(LimitsForCompilationRequest, limits) \
X(bool, disableSymbolRenaming) \
X(std::vector<InterstageLocationAndName>, interstageVariables) \
X(std::vector<std::string>, bufferBindingVariables) \
X(tint::glsl::writer::Options, tintOptions) \
X(CacheKey::UnsafeUnkeyedValue<dawn::platform::Platform*>, platform)
DAWN_MAKE_CACHE_REQUEST(GLSLCompilationRequest, GLSL_COMPILATION_REQUEST_MEMBERS);
#undef GLSL_COMPILATION_REQUEST_MEMBERS
#define GLSL_COMPILATION_MEMBERS(X) X(std::string, glsl)
DAWN_SERIALIZABLE(struct, GLSLCompilation, GLSL_COMPILATION_MEMBERS){};
#undef GLSL_COMPILATION_MEMBERS
} // namespace
} // namespace dawn::native
namespace dawn::native::opengl {
std::string GetBindingName(BindGroupIndex group, BindingNumber bindingNumber) {
std::ostringstream o;
o << "dawn_binding_" << static_cast<uint32_t>(group) << "_"
<< static_cast<uint32_t>(bindingNumber);
return o.str();
}
bool operator<(const BindingLocation& a, const BindingLocation& b) {
return std::tie(a.group, a.binding) < std::tie(b.group, b.binding);
}
bool operator<(const CombinedSampler& a, const CombinedSampler& b) {
return std::tie(a.usePlaceholderSampler, a.samplerLocation, a.textureLocation) <
std::tie(b.usePlaceholderSampler, a.samplerLocation, b.textureLocation);
}
std::string CombinedSampler::GetName() const {
std::ostringstream o;
o << "dawn_combined";
if (usePlaceholderSampler) {
o << "_placeholder_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();
}
// static
ResultOrError<Ref<ShaderModule>> ShaderModule::Create(
Device* device,
const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
ShaderModuleParseResult* parseResult,
OwnedCompilationMessages* compilationMessages) {
Ref<ShaderModule> module = AcquireRef(new ShaderModule(device, descriptor));
DAWN_TRY(module->Initialize(parseResult, compilationMessages));
return module;
}
ShaderModule::ShaderModule(Device* device, const UnpackedPtr<ShaderModuleDescriptor>& descriptor)
: ShaderModuleBase(device, descriptor) {}
MaybeError ShaderModule::Initialize(ShaderModuleParseResult* parseResult,
OwnedCompilationMessages* compilationMessages) {
ScopedTintICEHandler scopedICEHandler(GetDevice());
DAWN_TRY(InitializeBase(parseResult, compilationMessages));
return {};
}
ResultOrError<GLuint> ShaderModule::CompileShader(
const OpenGLFunctions& gl,
const ProgrammableStage& programmableStage,
SingleShaderStage stage,
bool usesInstanceIndex,
bool usesFragDepth,
CombinedSamplerInfo* combinedSamplers,
const PipelineLayout* layout,
bool* needsPlaceholderSampler,
bool* needsTextureBuiltinUniformBuffer,
BindingPointToFunctionAndOffset* bindingPointToData) const {
TRACE_EVENT0(GetDevice()->GetPlatform(), General, "TranslateToGLSL");
const OpenGLVersion& version = ToBackend(GetDevice())->GetGL().GetVersion();
GLSLCompilationRequest req = {};
auto tintProgram = GetTintProgram();
req.inputProgram = &(tintProgram->program);
using tint::BindingPoint;
// Since (non-Vulkan) GLSL does not support descriptor sets, generate a
// mapping from the original group/binding pair to a binding-only
// value. This mapping will be used by Tint to remap all global
// variables to the 1D space.
const EntryPointMetadata& entryPointMetaData = GetEntryPoint(programmableStage.entryPoint);
const BindingInfoArray& moduleBindingInfo = entryPointMetaData.bindings;
BindingMap glBindings;
BindingMap externalTextureExpansionMap;
for (BindGroupIndex group : IterateBitSet(layout->GetBindGroupLayoutsMask())) {
uint32_t groupAsInt = static_cast<uint32_t>(group);
const BindGroupLayoutInternalBase* bgl = layout->GetBindGroupLayout(group);
const auto& indices = layout->GetBindingIndexInfo()[group];
const auto& groupBindingInfo = moduleBindingInfo[group];
for (const auto& [bindingNumber, bindingInfo] : groupBindingInfo) {
BindingIndex bindingIndex = bgl->GetBindingIndex(bindingNumber);
GLuint shaderIndex = indices[bindingIndex];
BindingPoint srcBindingPoint{groupAsInt, static_cast<uint32_t>(bindingNumber)};
BindingPoint dstBindingPoint{0, shaderIndex};
if (srcBindingPoint != dstBindingPoint) {
glBindings.emplace(srcBindingPoint, dstBindingPoint);
}
// For buffer bindings that can be sharable across stages, we need to rename them to
// avoid GL program link failures due to block naming issues.
if (std::holds_alternative<BufferBindingInfo>(bindingInfo.bindingInfo) &&
stage != SingleShaderStage::Compute) {
req.bufferBindingVariables.emplace_back(bindingInfo.name);
}
}
for (const auto& [_, expansion] : bgl->GetExternalTextureBindingExpansionMap()) {
uint32_t plane1Slot = indices[bgl->GetBindingIndex(expansion.plane1)];
uint32_t paramsSlot = indices[bgl->GetBindingIndex(expansion.params)];
BindingPoint plane0{groupAsInt, static_cast<uint32_t>(expansion.plane0)};
BindingPoint plane1{groupAsInt, static_cast<uint32_t>(expansion.plane1)};
BindingPoint params{groupAsInt, static_cast<uint32_t>(expansion.params)};
glBindings.emplace(plane1, BindingPoint{0u, plane1Slot});
glBindings.emplace(params, BindingPoint{0u, paramsSlot});
externalTextureExpansionMap[plane0] = plane1;
}
}
tint::inspector::Inspector inspector(*req.inputProgram);
// Some texture builtin functions are unsupported on GLSL ES. These are emulated with internal
// uniforms.
tint::TextureBuiltinsFromUniformOptions textureBuiltinsFromUniform;
textureBuiltinsFromUniform.ubo_binding = {kMaxBindGroups + 1, 0};
auto textureBuiltinsFromUniformData = inspector.GetTextureQueries(programmableStage.entryPoint);
bool needsInternalUBO = false;
if (!textureBuiltinsFromUniformData.empty()) {
needsInternalUBO = true;
for (size_t i = 0; i < textureBuiltinsFromUniformData.size(); ++i) {
const auto& info = textureBuiltinsFromUniformData[i];
// This is the unmodified binding point from the WGSL shader.
BindingPoint srcBindingPoint{info.group, info.binding};
textureBuiltinsFromUniform.ubo_bindingpoint_ordering.emplace_back(srcBindingPoint);
// The remapped binding point is inserted into the Dawn data structure.
const BindGroupLayoutInternalBase* bgl =
layout->GetBindGroupLayout(BindGroupIndex{info.group});
BindingPoint dstBindingPoint = BindingPoint{
info.group,
static_cast<uint32_t>(bgl->GetBindingIndex(BindingNumber{info.binding}))};
BindPointFunction type = BindPointFunction::kTextureNumLevels;
switch (info.type) {
case tint::inspector::Inspector::TextureQueryType::kTextureNumLevels:
type = BindPointFunction::kTextureNumLevels;
break;
case tint::inspector::Inspector::TextureQueryType::kTextureNumSamples:
type = BindPointFunction::kTextureNumSamples;
break;
}
// Note, the `sizeof(uint32_t)` has to match up with the data type created by the
// `TextureBuiltinsFromUniform` when it creates the UBO structure.
bindingPointToData->emplace(
dstBindingPoint, std::pair{type, static_cast<uint32_t>(i * sizeof(uint32_t))});
}
}
// Remap the internal ubo binding as well.
glBindings.emplace(textureBuiltinsFromUniform.ubo_binding,
BindingPoint{0, layout->GetInternalUniformBinding()});
std::optional<tint::ast::transform::SubstituteOverride::Config> substituteOverrideConfig;
if (!programmableStage.metadata->overrides.empty()) {
substituteOverrideConfig = BuildSubstituteOverridesTransformConfig(programmableStage);
}
const CombinedLimits& limits = GetDevice()->GetLimits();
req.stage = stage;
req.entryPointName = programmableStage.entryPoint;
req.substituteOverrideConfig = std::move(substituteOverrideConfig);
req.limits = LimitsForCompilationRequest::Create(limits.v1);
req.platform = UnsafeUnkeyedValue(GetDevice()->GetPlatform());
req.tintOptions.version = tint::glsl::writer::Version(ToTintGLStandard(version.GetStandard()),
version.GetMajor(), version.GetMinor());
req.tintOptions.disable_robustness = false;
if (usesInstanceIndex) {
req.tintOptions.first_instance_offset =
4 * PipelineLayout::PushConstantLocation::FirstInstance;
}
if (usesFragDepth) {
req.tintOptions.depth_range_offsets = {4 * PipelineLayout::PushConstantLocation::MinDepth,
4 * PipelineLayout::PushConstantLocation::MaxDepth};
}
req.disableSymbolRenaming = GetDevice()->IsToggleEnabled(Toggle::DisableSymbolRenaming);
req.interstageVariables = {};
for (size_t i = 0; i < entryPointMetaData.interStageVariables.size(); i++) {
if (entryPointMetaData.usedInterStageVariables[i]) {
req.interstageVariables.emplace_back(static_cast<uint32_t>(i),
entryPointMetaData.interStageVariables[i].name);
}
}
req.tintOptions.external_texture_options = BuildExternalTextureTransformBindings(layout);
req.tintOptions.binding_remapper_options.binding_points = std::move(glBindings);
req.tintOptions.texture_builtins_from_uniform = std::move(textureBuiltinsFromUniform);
req.tintOptions.disable_polyfill_integer_div_mod =
GetDevice()->IsToggleEnabled(Toggle::DisablePolyfillsOnIntegerDivisonAndModulo);
// When textures are accessed without a sampler (e.g., textureLoad()),
// GetSamplerTextureUses() will return this sentinel value.
BindingPoint placeholderBindingPoint{static_cast<uint32_t>(kMaxBindGroupsTyped), 0};
*needsPlaceholderSampler = false;
// Find all the sampler/texture pairs for this entry point, and create
// CombinedSamplers for them. CombinedSampler records the binding points
// of the original texture and sampler, and generates a unique name. The
// corresponding uniforms will be retrieved by these generated names
// in PipelineGL. Any texture-only references will have
// "usePlaceholderSampler" set to true, and only the texture binding point
// will be used in naming them. In addition, Dawn will bind a
// non-filtering sampler for them (see PipelineGL).
auto uses =
inspector.GetSamplerTextureUses(programmableStage.entryPoint, placeholderBindingPoint);
CombinedSamplerInfo combinedSamplerInfo;
for (const auto& use : uses) {
CombinedSampler* info =
AppendCombinedSampler(&combinedSamplerInfo, use.texture_binding_point,
use.sampler_binding_point, placeholderBindingPoint);
if (info->usePlaceholderSampler) {
*needsPlaceholderSampler = true;
req.tintOptions.combined_samplers_info.placeholder_sampler_binding =
placeholderBindingPoint;
}
tint::glsl::writer::binding::CombinedTextureSamplerPair pair;
pair.texture = use.texture_binding_point;
pair.sampler = use.sampler_binding_point;
req.tintOptions.combined_samplers_info.sampler_texture_to_name[pair] = info->GetName();
// If the texture has an associated plane1 texture (ie., it's an external texture),
// append a new combined sampler with the same sampler and the plane1 texture.
BindingMap::iterator plane1Texture =
externalTextureExpansionMap.find(use.texture_binding_point);
if (plane1Texture != externalTextureExpansionMap.end()) {
tint::glsl::writer::binding::CombinedTextureSamplerPair plane1Use{
plane1Texture->second, use.sampler_binding_point};
CombinedSampler* plane1Info =
AppendCombinedSampler(&combinedSamplerInfo, plane1Use.texture, plane1Use.sampler,
placeholderBindingPoint);
req.tintOptions.combined_samplers_info.sampler_texture_to_name[plane1Use] =
plane1Info->GetName();
}
}
CacheResult<GLSLCompilation> compilationResult;
DAWN_TRY_LOAD_OR_RUN(
compilationResult, GetDevice(), std::move(req), GLSLCompilation::FromBlob,
[](GLSLCompilationRequest r) -> ResultOrError<GLSLCompilation> {
tint::ast::transform::Manager transformManager;
tint::ast::transform::DataMap transformInputs;
transformManager.Add<tint::ast::transform::SingleEntryPoint>();
transformInputs.Add<tint::ast::transform::SingleEntryPoint::Config>(r.entryPointName);
{
tint::ast::transform::Renamer::Remappings assignedRenamings = {};
// Give explicit renaming mappings for interstage variables
// Because GLSL requires interstage IO names to match.
for (const auto& it : r.interstageVariables) {
assignedRenamings.emplace(
it.second, "dawn_interstage_location_" + std::to_string(it.first));
}
// Prepend v_ or f_ to buffer binding variable names in order to avoid collisions in
// renamed interface blocks. The AddBlockAttribute transform in the Tint GLSL
// printer will always generate wrapper structs from such bindings.
for (const auto& variableName : r.bufferBindingVariables) {
assignedRenamings.emplace(
variableName,
(r.stage == SingleShaderStage::Vertex ? "v_" : "f_") + variableName);
}
// Needs to run early so that they can use builtin names safely.
// TODO(dawn:2180): move this transform into Tint.
transformManager.Add<tint::ast::transform::Renamer>();
transformInputs.Add<tint::ast::transform::Renamer::Config>(
r.disableSymbolRenaming ? tint::ast::transform::Renamer::Target::kGlslKeywords
: tint::ast::transform::Renamer::Target::kAll,
false, std::move(assignedRenamings));
}
if (r.substituteOverrideConfig) {
// This needs to run after SingleEntryPoint transform which removes unused overrides
// for current entry point.
transformManager.Add<tint::ast::transform::SubstituteOverride>();
transformInputs.Add<tint::ast::transform::SubstituteOverride::Config>(
std::move(r.substituteOverrideConfig).value());
}
tint::Program program;
tint::ast::transform::DataMap transformOutputs;
DAWN_TRY_ASSIGN(program, RunTransforms(&transformManager, r.inputProgram,
transformInputs, &transformOutputs, nullptr));
// TODO(dawn:2180): refactor out.
// Get the entry point name after the renamer pass.
// In the case of the entry-point name being a reserved GLSL keyword
// (including `main`) the entry-point would have been renamed
// regardless of the `disableSymbolRenaming` flag. Always check the
// rename map, and if the name was changed, get the new one.
auto* data = transformOutputs.Get<tint::ast::transform::Renamer::Data>();
DAWN_ASSERT(data != nullptr);
auto it = data->remappings.find(r.entryPointName.data());
std::string remappedEntryPoint;
if (it != data->remappings.end()) {
remappedEntryPoint = it->second;
} else {
remappedEntryPoint = r.entryPointName;
}
DAWN_ASSERT(remappedEntryPoint != "");
if (r.stage == SingleShaderStage::Compute) {
// Validate workgroup size after program runs transforms.
Extent3D _;
DAWN_TRY_ASSIGN(_, ValidateComputeStageWorkgroupSize(
program, remappedEntryPoint.c_str(), r.limits,
/* fullSubgroups */ {}));
}
auto result = tint::glsl::writer::Generate(program, r.tintOptions, remappedEntryPoint);
DAWN_INVALID_IF(result != tint::Success, "An error occurred while generating GLSL:\n%s",
result.Failure().reason.Str());
return GLSLCompilation{{std::move(result->glsl)}};
},
"OpenGL.CompileShaderToGLSL");
if (GetDevice()->IsToggleEnabled(Toggle::DumpShaders)) {
std::ostringstream dumpedMsg;
dumpedMsg << "/* Dumped generated GLSL */" << std::endl << compilationResult->glsl;
GetDevice()->EmitLog(WGPULoggingType_Info, dumpedMsg.str().c_str());
}
GLuint shader = gl.CreateShader(GLShaderType(stage));
const char* source = compilationResult->glsl.c_str();
gl.ShaderSource(shader, 1, &source, nullptr);
gl.CompileShader(shader);
GLint compileStatus = GL_FALSE;
gl.GetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
if (compileStatus == GL_FALSE) {
GLint infoLogLength = 0;
gl.GetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
if (infoLogLength > 1) {
std::vector<char> buffer(infoLogLength);
gl.GetShaderInfoLog(shader, infoLogLength, nullptr, &buffer[0]);
gl.DeleteShader(shader);
return DAWN_VALIDATION_ERROR("%s\nProgram compilation failed:\n%s", source,
buffer.data());
}
}
GetDevice()->GetBlobCache()->EnsureStored(compilationResult);
*needsTextureBuiltinUniformBuffer = needsInternalUBO;
*combinedSamplers = std::move(combinedSamplerInfo);
return shader;
}
} // namespace dawn::native::opengl