diff --git a/src/dawn/native/opengl/OpenGLVersion.cpp b/src/dawn/native/opengl/OpenGLVersion.cpp index 2c3f0a981e..80857064bd 100644 --- a/src/dawn/native/opengl/OpenGLVersion.cpp +++ b/src/dawn/native/opengl/OpenGLVersion.cpp @@ -62,6 +62,10 @@ bool OpenGLVersion::IsES() const { return mStandard == Standard::ES; } +OpenGLVersion::Standard OpenGLVersion::GetStandard() const { + return mStandard; +} + uint32_t OpenGLVersion::GetMajor() const { return mMajorVersion; } diff --git a/src/dawn/native/opengl/OpenGLVersion.h b/src/dawn/native/opengl/OpenGLVersion.h index a9a296fb1f..5f490abc99 100644 --- a/src/dawn/native/opengl/OpenGLVersion.h +++ b/src/dawn/native/opengl/OpenGLVersion.h @@ -21,19 +21,21 @@ namespace dawn::native::opengl { struct OpenGLVersion { public: + enum class Standard { + Desktop, + ES, + }; + MaybeError Initialize(GetProcAddress getProc); bool IsDesktop() const; bool IsES() const; + Standard GetStandard() const; uint32_t GetMajor() const; uint32_t GetMinor() const; bool IsAtLeast(uint32_t majorVersion, uint32_t minorVersion) const; private: - enum class Standard { - Desktop, - ES, - }; uint32_t mMajorVersion; uint32_t mMinorVersion; Standard mStandard; diff --git a/src/dawn/native/opengl/PipelineGL.cpp b/src/dawn/native/opengl/PipelineGL.cpp index 2ddabce665..701102c6bc 100644 --- a/src/dawn/native/opengl/PipelineGL.cpp +++ b/src/dawn/native/opengl/PipelineGL.cpp @@ -30,22 +30,6 @@ namespace dawn::native::opengl { -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; - } - UNREACHABLE(); -} - -} // namespace - PipelineGL::PipelineGL() : mProgram(0) {} PipelineGL::~PipelineGL() = default; @@ -53,28 +37,6 @@ PipelineGL::~PipelineGL() = default; MaybeError PipelineGL::InitializeBase(const OpenGLFunctions& gl, const PipelineLayout* layout, const PerStage& stages) { - auto CreateShader = [](const OpenGLFunctions& gl, GLenum type, - const char* source) -> ResultOrError { - GLuint shader = gl.CreateShader(type); - 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 buffer(infoLogLength); - gl.GetShaderInfoLog(shader, infoLogLength, nullptr, &buffer[0]); - return DAWN_FORMAT_VALIDATION_ERROR("%s\nProgram compilation failed:\n%s", source, - buffer.data()); - } - } - return shader; - }; - mProgram = gl.CreateProgram(); // Compute the set of active stages. @@ -91,12 +53,10 @@ MaybeError PipelineGL::InitializeBase(const OpenGLFunctions& gl, std::vector glShaders; for (SingleShaderStage stage : IterateStages(activeStages)) { const ShaderModule* module = ToBackend(stages[stage].module.Get()); - std::string glsl; - DAWN_TRY_ASSIGN(glsl, module->TranslateToGLSL(stages[stage].entryPoint.c_str(), stage, - &combinedSamplers[stage], layout, - &needsPlaceholderSampler)); GLuint shader; - DAWN_TRY_ASSIGN(shader, CreateShader(gl, GLShaderType(stage), glsl.c_str())); + DAWN_TRY_ASSIGN(shader, + module->CompileShader(gl, stages[stage], stage, &combinedSamplers[stage], + layout, &needsPlaceholderSampler)); gl.AttachShader(mProgram, shader); glShaders.push_back(shader); } diff --git a/src/dawn/native/opengl/ShaderModuleGL.cpp b/src/dawn/native/opengl/ShaderModuleGL.cpp index 8e85076a9b..30ca69293a 100644 --- a/src/dawn/native/opengl/ShaderModuleGL.cpp +++ b/src/dawn/native/opengl/ShaderModuleGL.cpp @@ -18,14 +18,110 @@ #include #include "dawn/native/BindGroupLayout.h" +#include "dawn/native/CacheRequest.h" +#include "dawn/native/Pipeline.h" #include "dawn/native/TintUtils.h" #include "dawn/native/opengl/DeviceGL.h" #include "dawn/native/opengl/PipelineLayoutGL.h" +#include "dawn/native/stream/BlobSource.h" +#include "dawn/native/stream/ByteVectorSink.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; + } + UNREACHABLE(); +} + +tint::writer::glsl::Version::Standard ToTintGLStandard(opengl::OpenGLVersion::Standard standard) { + switch (standard) { + case opengl::OpenGLVersion::Standard::Desktop: + return tint::writer::glsl::Version::Standard::kDesktop; + case opengl::OpenGLVersion::Standard::ES: + return tint::writer::glsl::Version::Standard::kES; + } +} + +using BindingMap = std::unordered_map; + +#define GLSL_COMPILATION_REQUEST_MEMBERS(X) \ + X(const tint::Program*, inputProgram) \ + X(std::string, entryPointName) \ + X(tint::transform::MultiplanarExternalTexture::BindingsMap, externalTextureBindings) \ + X(BindingMap, glBindings) \ + X(opengl::OpenGLVersion::Standard, glVersionStandard) \ + X(uint32_t, glVersionMajor) \ + X(uint32_t, glVersionMinor) + +DAWN_MAKE_CACHE_REQUEST(GLSLCompilationRequest, GLSL_COMPILATION_REQUEST_MEMBERS); +#undef GLSL_COMPILATION_REQUEST_MEMBERS + +#define GLSL_COMPILATION_MEMBERS(X) \ + X(std::string, glsl) \ + X(bool, needsPlaceholderSampler) \ + X(opengl::CombinedSamplerInfo, combinedSamplerInfo) +struct GLSLCompilation { + DAWN_VISITABLE_MEMBERS(GLSL_COMPILATION_MEMBERS) +#undef GLSL_COMPILATION_MEMBERS + + static ResultOrError FromBlob(Blob blob) { + stream::BlobSource source(std::move(blob)); + GLSLCompilation out; + DAWN_TRY(out.VisitAll([&](auto&... members) { return StreamOut(&source, &members...); })); + return out; + } +}; + +} // namespace + +template <> +void BlobCache::Store(const CacheKey& key, const GLSLCompilation& c) { + stream::ByteVectorSink sink; + c.VisitAll([&](const auto&... members) { StreamIn(&sink, members...); }); + Store(key, CreateBlob(std::move(sink))); +} + +template <> +void stream::Stream::Write( + stream::Sink* s, + const opengl::BindingLocation& bindingLocation) { + bindingLocation.VisitAll([&](auto&... members) { return StreamIn(s, members...); }); +} + +template <> +MaybeError stream::Stream::Read(stream::Source* s, + opengl::BindingLocation* bindingLocation) { + return bindingLocation->VisitAll([&](auto&... members) { return StreamOut(s, &members...); }); +} + +template <> +void stream::Stream::Write( + stream::Sink* s, + const opengl::CombinedSampler& combinedSampler) { + combinedSampler.VisitAll([&](auto&... members) { return StreamIn(s, members...); }); +} + +template <> +MaybeError stream::Stream::Read(stream::Source* s, + opengl::CombinedSampler* combinedSampler) { + return combinedSampler->VisitAll([&](auto&... members) { return StreamOut(s, &members...); }); +} + +} // namespace dawn::native + namespace dawn::native::opengl { std::string GetBindingName(BindGroupIndex group, BindingNumber bindingNumber) { @@ -81,104 +177,146 @@ MaybeError ShaderModule::Initialize(ShaderModuleParseResult* parseResult, return {}; } -ResultOrError ShaderModule::TranslateToGLSL(const char* entryPointName, - SingleShaderStage stage, - CombinedSamplerInfo* combinedSamplers, - const PipelineLayout* layout, - bool* needsPlaceholderSampler) const { +ResultOrError ShaderModule::CompileShader(const OpenGLFunctions& gl, + const ProgrammableStage& programmableStage, + SingleShaderStage stage, + CombinedSamplerInfo* combinedSamplers, + const PipelineLayout* layout, + bool* needsPlaceholderSampler) const { TRACE_EVENT0(GetDevice()->GetPlatform(), General, "TranslateToGLSL"); - tint::transform::Manager transformManager; - tint::transform::DataMap transformInputs; - auto externalTextureBindings = BuildExternalTextureTransformBindings(layout); - if (!externalTextureBindings.empty()) { - transformManager.Add(); - transformInputs.Add( - std::move(externalTextureBindings)); - } - - tint::Program program; - DAWN_TRY_ASSIGN(program, RunTransforms(&transformManager, GetTintProgram(), transformInputs, - nullptr, nullptr)); const OpenGLVersion& version = ToBackend(GetDevice())->GetGL().GetVersion(); - tint::writer::glsl::Options tintOptions; - using Version = tint::writer::glsl::Version; - tintOptions.version = - Version(version.IsDesktop() ? Version::Standard::kDesktop : Version::Standard::kES, - version.GetMajor(), version.GetMinor()); - using tint::transform::BindingPoint; - // When textures are accessed without a sampler (e.g., textureLoad()), - // GetSamplerTextureUses() will return this sentinel value. - BindingPoint placeholderBindingPoint{static_cast(kMaxBindGroupsTyped), 0}; - - tint::inspector::Inspector inspector(&program); - // 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(entryPointName, placeholderBindingPoint); - for (const auto& use : uses) { - combinedSamplers->emplace_back(); - - CombinedSampler* info = &combinedSamplers->back(); - if (use.sampler_binding_point == placeholderBindingPoint) { - info->usePlaceholderSampler = true; - *needsPlaceholderSampler = true; - } else { - info->usePlaceholderSampler = false; - } - info->samplerLocation.group = BindGroupIndex(use.sampler_binding_point.group); - info->samplerLocation.binding = BindingNumber(use.sampler_binding_point.binding); - info->textureLocation.group = BindGroupIndex(use.texture_binding_point.group); - info->textureLocation.binding = BindingNumber(use.texture_binding_point.binding); - tintOptions.binding_map[use] = info->GetName(); - } - if (*needsPlaceholderSampler) { - tintOptions.placeholder_binding_point = placeholderBindingPoint; - } - // 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 BindingInfoArray& moduleBindingInfo = + GetEntryPoint(programmableStage.entryPoint).bindings; + std::unordered_map glBindings; for (BindGroupIndex group : IterateBitSet(layout->GetBindGroupLayoutsMask())) { - const BindGroupLayoutBase::BindingMap& bindingMap = - layout->GetBindGroupLayout(group)->GetBindingMap(); - for (const auto& it : bindingMap) { - BindingNumber bindingNumber = it.first; - BindingIndex bindingIndex = it.second; - const BindingInfo& bindingInfo = - layout->GetBindGroupLayout(group)->GetBindingInfo(bindingIndex); - if (!(bindingInfo.visibility & StageBit(stage))) { - continue; - } - - uint32_t shaderIndex = layout->GetBindingIndexInfo()[group][bindingIndex]; + const BindGroupLayoutBase* bgl = layout->GetBindGroupLayout(group); + const auto& groupBindingInfo = moduleBindingInfo[group]; + for (const auto& [bindingNumber, bindingInfo] : groupBindingInfo) { + BindingIndex bindingIndex = bgl->GetBindingIndex(bindingNumber); + GLuint shaderIndex = layout->GetBindingIndexInfo()[group][bindingIndex]; BindingPoint srcBindingPoint{static_cast(group), static_cast(bindingNumber)}; BindingPoint dstBindingPoint{0, shaderIndex}; - tintOptions.binding_points.emplace(srcBindingPoint, dstBindingPoint); + if (srcBindingPoint != dstBindingPoint) { + glBindings.emplace(srcBindingPoint, dstBindingPoint); + } } - tintOptions.allow_collisions = true; } - auto result = tint::writer::glsl::Generate(&program, tintOptions, entryPointName); - DAWN_INVALID_IF(!result.success, "An error occured while generating GLSL: %s.", result.error); - std::string glsl = std::move(result.glsl); + + GLSLCompilationRequest req = {}; + req.inputProgram = GetTintProgram(); + req.entryPointName = programmableStage.entryPoint; + req.externalTextureBindings = BuildExternalTextureTransformBindings(layout); + req.glBindings = std::move(glBindings); + req.glVersionStandard = version.GetStandard(); + req.glVersionMajor = version.GetMajor(); + req.glVersionMinor = version.GetMinor(); + + CacheResult compilationResult; + DAWN_TRY_LOAD_OR_RUN( + compilationResult, GetDevice(), std::move(req), GLSLCompilation::FromBlob, + [](GLSLCompilationRequest r) -> ResultOrError { + tint::transform::Manager transformManager; + tint::transform::DataMap transformInputs; + + if (!r.externalTextureBindings.empty()) { + transformManager.Add(); + transformInputs.Add( + std::move(r.externalTextureBindings)); + } + + tint::Program program; + DAWN_TRY_ASSIGN(program, RunTransforms(&transformManager, r.inputProgram, + transformInputs, nullptr, nullptr)); + + tint::writer::glsl::Options tintOptions; + tintOptions.version = tint::writer::glsl::Version(ToTintGLStandard(r.glVersionStandard), + r.glVersionMajor, r.glVersionMinor); + + // When textures are accessed without a sampler (e.g., textureLoad()), + // GetSamplerTextureUses() will return this sentinel value. + BindingPoint placeholderBindingPoint{static_cast(kMaxBindGroupsTyped), 0}; + + bool needsPlaceholderSampler = false; + tint::inspector::Inspector inspector(&program); + // 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(r.entryPointName, placeholderBindingPoint); + CombinedSamplerInfo combinedSamplerInfo; + for (const auto& use : uses) { + combinedSamplerInfo.emplace_back(); + + CombinedSampler* info = &combinedSamplerInfo.back(); + if (use.sampler_binding_point == placeholderBindingPoint) { + info->usePlaceholderSampler = true; + needsPlaceholderSampler = true; + tintOptions.placeholder_binding_point = placeholderBindingPoint; + } else { + info->usePlaceholderSampler = false; + } + info->samplerLocation.group = BindGroupIndex(use.sampler_binding_point.group); + info->samplerLocation.binding = BindingNumber(use.sampler_binding_point.binding); + info->textureLocation.group = BindGroupIndex(use.texture_binding_point.group); + info->textureLocation.binding = BindingNumber(use.texture_binding_point.binding); + tintOptions.binding_map[use] = info->GetName(); + } + tintOptions.binding_points = std::move(r.glBindings); + tintOptions.allow_collisions = true; + + auto result = tint::writer::glsl::Generate(&program, tintOptions, r.entryPointName); + DAWN_INVALID_IF(!result.success, "An error occured while generating GLSL: %s.", + result.error); + + return GLSLCompilation{std::move(result.glsl), needsPlaceholderSampler, + std::move(combinedSamplerInfo)}; + }); if (GetDevice()->IsToggleEnabled(Toggle::DumpShaders)) { std::ostringstream dumpedMsg; - dumpedMsg << "/* Dumped generated GLSL */" << std::endl << glsl; + dumpedMsg << "/* Dumped generated GLSL */" << std::endl << compilationResult->glsl; GetDevice()->EmitLog(WGPULoggingType_Info, dumpedMsg.str().c_str()); } - return glsl; + 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 buffer(infoLogLength); + gl.GetShaderInfoLog(shader, infoLogLength, nullptr, &buffer[0]); + gl.DeleteShader(shader); + return DAWN_FORMAT_VALIDATION_ERROR("%s\nProgram compilation failed:\n%s", source, + buffer.data()); + } + } + + if (BlobCache* cache = GetDevice()->GetBlobCache()) { + cache->EnsureStored(compilationResult); + } + *needsPlaceholderSampler = compilationResult->needsPlaceholderSampler; + *combinedSamplers = std::move(compilationResult->combinedSamplerInfo); + return shader; } } // namespace dawn::native::opengl diff --git a/src/dawn/native/opengl/ShaderModuleGL.h b/src/dawn/native/opengl/ShaderModuleGL.h index 4dcff097fe..838d7e8a5d 100644 --- a/src/dawn/native/opengl/ShaderModuleGL.h +++ b/src/dawn/native/opengl/ShaderModuleGL.h @@ -21,37 +21,53 @@ #include #include "dawn/native/ShaderModule.h" - +#include "dawn/native/VisitableMembers.h" #include "dawn/native/opengl/opengl_platform.h" -namespace dawn::native::opengl { +namespace dawn::native { + +struct ProgrammableStage; + +namespace stream { +class Sink; +class Source; +} // namespace stream + +namespace opengl { class Device; class PipelineLayout; +struct OpenGLFunctions; std::string GetBindingName(BindGroupIndex group, BindingNumber bindingNumber); +#define BINDING_LOCATION_MEMBERS(X) \ + X(BindGroupIndex, group) \ + X(BindingNumber, binding) struct BindingLocation { - BindGroupIndex group; - BindingNumber binding; + DAWN_VISITABLE_MEMBERS(BINDING_LOCATION_MEMBERS) +#undef BINDING_LOCATION_MEMBERS }; bool operator<(const BindingLocation& a, const BindingLocation& b); +#define COMBINED_SAMPLER_MEMBERS(X) \ + X(BindingLocation, samplerLocation) \ + X(BindingLocation, textureLocation) \ + /* OpenGL requires a sampler with texelFetch. If this is true, the developer did not */ \ + /* provide one and Dawn should bind a placeholder non-filtering sampler; */ \ + /* |samplerLocation| is unused. */ \ + X(bool, usePlaceholderSampler) + 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 placeholder non-filtering sampler. |samplerLocation| is - // unused. - bool usePlaceholderSampler; + DAWN_VISITABLE_MEMBERS(COMBINED_SAMPLER_MEMBERS) +#undef COMBINED_SAMPLER_MEMBERS + std::string GetName() const; }; bool operator<(const CombinedSampler& a, const CombinedSampler& b); using CombinedSamplerInfo = std::vector; -using BindingInfoArrayTable = std::unordered_map>; - class ShaderModule final : public ShaderModuleBase { public: static ResultOrError> Create(Device* device, @@ -59,11 +75,12 @@ class ShaderModule final : public ShaderModuleBase { ShaderModuleParseResult* parseResult, OwnedCompilationMessages* compilationMessages); - ResultOrError TranslateToGLSL(const char* entryPointName, - SingleShaderStage stage, - CombinedSamplerInfo* combinedSamplers, - const PipelineLayout* layout, - bool* needsPlaceholderSampler) const; + ResultOrError CompileShader(const OpenGLFunctions& gl, + const ProgrammableStage& programmableStage, + SingleShaderStage stage, + CombinedSamplerInfo* combinedSamplers, + const PipelineLayout* layout, + bool* needsPlaceholderSampler) const; private: ShaderModule(Device* device, const ShaderModuleDescriptor* descriptor); @@ -72,6 +89,7 @@ class ShaderModule final : public ShaderModuleBase { OwnedCompilationMessages* compilationMessages); }; -} // namespace dawn::native::opengl +} // namespace opengl +} // namespace dawn::native #endif // SRC_DAWN_NATIVE_OPENGL_SHADERMODULEGL_H_ diff --git a/src/dawn/tests/end2end/PipelineCachingTests.cpp b/src/dawn/tests/end2end/PipelineCachingTests.cpp index cbf5a92f29..52bfa174e8 100644 --- a/src/dawn/tests/end2end/PipelineCachingTests.cpp +++ b/src/dawn/tests/end2end/PipelineCachingTests.cpp @@ -108,8 +108,8 @@ class PipelineCachingTests : public DawnTest { const EntryCounts counts = { // pipeline caching is only implemented on D3D12/Vulkan IsD3D12() || IsVulkan() ? 1u : 0u, - // shader module caching is only implemented on Vulkan/D3D12/Metal - IsVulkan() || IsMetal() || IsD3D12() ? 1u : 0u, + // One blob per shader module + 1u, }; NiceMock mMockCache; }; @@ -587,8 +587,8 @@ TEST_P(SinglePipelineCachingTests, RenderPipelineBlobCacheLayout) { } // Cache should not hit for the fragment shader, but should hit for the pipeline. - // Except for D3D12, the shader is different but compiles to the same due to binding number - // remapping. + // On Metal and Vulkan, the shader is different but compiles to the same due to binding number + // remapping. On other backends, the compiled shader is different and so is the pipeline. { wgpu::Device device = CreateDevice(); utils::ComboRenderPipelineDescriptor desc; @@ -605,7 +605,7 @@ TEST_P(SinglePipelineCachingTests, RenderPipelineBlobCacheLayout) { {1, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform}, }), }); - if (!IsD3D12()) { + if (IsMetal() || IsVulkan()) { EXPECT_CACHE_STATS(mMockCache, Hit(counts.shaderModule + counts.pipeline), Add(counts.shaderModule), device.CreateRenderPipeline(&desc)); } else {