Cache WGSL -> GLSL compilation

Fixed: dawn:1480
Change-Id: I5c1453a4e37d0e97805391a6c807091d0a8fc1c7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/98281
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
Austin Eng 2022-08-09 17:22:22 +00:00 committed by Dawn LUCI CQ
parent e62fbbc75c
commit 8830991b41
6 changed files with 268 additions and 146 deletions

View File

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

View File

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

View File

@ -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<ProgrammableStage>& stages) {
auto CreateShader = [](const OpenGLFunctions& gl, GLenum type,
const char* source) -> ResultOrError<GLuint> {
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<char> 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<GLuint> 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);
}

View File

@ -18,14 +18,110 @@
#include <utility>
#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<tint::sem::BindingPoint, tint::sem::BindingPoint>;
#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<GLSLCompilation> 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<GLSLCompilation>(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<opengl::BindingLocation>::Write(
stream::Sink* s,
const opengl::BindingLocation& bindingLocation) {
bindingLocation.VisitAll([&](auto&... members) { return StreamIn(s, members...); });
}
template <>
MaybeError stream::Stream<opengl::BindingLocation>::Read(stream::Source* s,
opengl::BindingLocation* bindingLocation) {
return bindingLocation->VisitAll([&](auto&... members) { return StreamOut(s, &members...); });
}
template <>
void stream::Stream<opengl::CombinedSampler>::Write(
stream::Sink* s,
const opengl::CombinedSampler& combinedSampler) {
combinedSampler.VisitAll([&](auto&... members) { return StreamIn(s, members...); });
}
template <>
MaybeError stream::Stream<opengl::CombinedSampler>::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<std::string> ShaderModule::TranslateToGLSL(const char* entryPointName,
SingleShaderStage stage,
CombinedSamplerInfo* combinedSamplers,
const PipelineLayout* layout,
bool* needsPlaceholderSampler) const {
ResultOrError<GLuint> 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<tint::transform::MultiplanarExternalTexture>();
transformInputs.Add<tint::transform::MultiplanarExternalTexture::NewBindingPoints>(
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<uint32_t>(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<tint::sem::BindingPoint, tint::sem::BindingPoint> 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<uint32_t>(group),
static_cast<uint32_t>(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<GLSLCompilation> compilationResult;
DAWN_TRY_LOAD_OR_RUN(
compilationResult, GetDevice(), std::move(req), GLSLCompilation::FromBlob,
[](GLSLCompilationRequest r) -> ResultOrError<GLSLCompilation> {
tint::transform::Manager transformManager;
tint::transform::DataMap transformInputs;
if (!r.externalTextureBindings.empty()) {
transformManager.Add<tint::transform::MultiplanarExternalTexture>();
transformInputs.Add<tint::transform::MultiplanarExternalTexture::NewBindingPoints>(
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<uint32_t>(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<char> 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

View File

@ -21,37 +21,53 @@
#include <vector>
#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<CombinedSampler>;
using BindingInfoArrayTable = std::unordered_map<std::string, std::unique_ptr<BindingInfoArray>>;
class ShaderModule final : public ShaderModuleBase {
public:
static ResultOrError<Ref<ShaderModule>> Create(Device* device,
@ -59,11 +75,12 @@ class ShaderModule final : public ShaderModuleBase {
ShaderModuleParseResult* parseResult,
OwnedCompilationMessages* compilationMessages);
ResultOrError<std::string> TranslateToGLSL(const char* entryPointName,
SingleShaderStage stage,
CombinedSamplerInfo* combinedSamplers,
const PipelineLayout* layout,
bool* needsPlaceholderSampler) const;
ResultOrError<GLuint> 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_

View File

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