blob: a00c6dc361f297cfeaf707c6c4b235c66fd217af [file] [log] [blame] [edit]
//
// Copyright 2022 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// PLSProgramCache.cpp: Implements a cache of native programs used to render load/store operations
// for EXT_shader_pixel_local_storage.
#include "libANGLE/renderer/gl/PLSProgramCache.h"
#include "libANGLE/Caps.h"
#include "libANGLE/PixelLocalStorage.h"
#include "libANGLE/renderer/gl/FunctionsGL.h"
namespace rx
{
namespace
{
GLuint CreateVAO(const FunctionsGL *gl)
{
GLuint vao;
gl->genVertexArrays(1, &vao);
return vao;
}
bool IsShaderCompiled(const FunctionsGL *gl, GLuint shaderID)
{
GLint isCompiled = 0;
gl->getShaderiv(shaderID, GL_COMPILE_STATUS, &isCompiled);
return isCompiled != 0;
}
bool IsProgramLinked(const FunctionsGL *gl, GLuint programID)
{
GLint isLinked = 0;
gl->getProgramiv(programID, GL_LINK_STATUS, &isLinked);
return isLinked != 0;
}
PLSFormatKey GetPLSFormatKey(GLenum internalformat)
{
switch (internalformat)
{
default:
UNREACHABLE();
[[fallthrough]];
case GL_NONE:
return PLSFormatKey::INACTIVE;
case GL_RGBA8:
return PLSFormatKey::RGBA8;
case GL_RGBA8I:
return PLSFormatKey::RGBA8I;
case GL_RGBA8UI:
return PLSFormatKey::RGBA8UI;
case GL_R32F:
return PLSFormatKey::R32F;
case GL_R32UI:
return PLSFormatKey::R32UI;
}
}
constexpr uint64_t BitRepeat(uint64_t bits, int interval)
{
return interval >= 64 ? bits : BitRepeat(bits | (bits << interval), interval * 2);
}
const char *GetPLSQualifier(PLSProgramType type)
{
switch (type)
{
case PLSProgramType::Load:
return "__pixel_local_outEXT";
case PLSProgramType::Store:
return "__pixel_local_inEXT";
default:
UNREACHABLE();
return "";
}
}
const char *GetFormatQualifier(PLSFormatKey formatKey)
{
switch (formatKey)
{
case PLSFormatKey::RGBA8:
return "rgba8";
case PLSFormatKey::RGBA8I:
return "rgba8i";
case PLSFormatKey::RGBA8UI:
return "rgba8ui";
case PLSFormatKey::R32F:
return "r32f";
case PLSFormatKey::R32UI:
return "r32ui";
default:
UNREACHABLE();
return "";
}
}
const char *GetFormatPrecision(PLSFormatKey formatKey)
{
switch (formatKey)
{
case PLSFormatKey::RGBA8:
case PLSFormatKey::RGBA8I:
case PLSFormatKey::RGBA8UI:
return "lowp";
case PLSFormatKey::R32F:
case PLSFormatKey::R32UI:
return "highp";
default:
UNREACHABLE();
return "";
}
}
const char *GetFormatRawType(PLSFormatKey formatKey)
{
switch (formatKey)
{
case PLSFormatKey::RGBA8:
return "vec4";
case PLSFormatKey::RGBA8I:
return "ivec4";
case PLSFormatKey::RGBA8UI:
return "uvec4";
case PLSFormatKey::R32F:
return "float";
case PLSFormatKey::R32UI:
return "uint";
default:
UNREACHABLE();
return "";
}
}
const char *GetFormatPrefix(PLSFormatKey formatKey)
{
switch (formatKey)
{
case PLSFormatKey::RGBA8:
case PLSFormatKey::R32F:
return "";
case PLSFormatKey::RGBA8I:
return "i";
case PLSFormatKey::RGBA8UI:
case PLSFormatKey::R32UI:
return "u";
default:
UNREACHABLE();
return "";
}
}
const char *GetFormatSwizzle(PLSFormatKey formatKey)
{
switch (formatKey)
{
case PLSFormatKey::RGBA8:
case PLSFormatKey::RGBA8I:
case PLSFormatKey::RGBA8UI:
return "";
case PLSFormatKey::R32F:
case PLSFormatKey::R32UI:
return ".r";
default:
UNREACHABLE();
return "";
}
}
void ExpandPLSVar(std::ostringstream &out, int binding, PLSFormatKey formatKey)
{
switch (formatKey)
{
case PLSFormatKey::RGBA8:
case PLSFormatKey::RGBA8I:
case PLSFormatKey::RGBA8UI:
out << "pls._" << binding;
break;
case PLSFormatKey::R32F:
out << "vec4(pls._" << binding << ",0,0,1)";
break;
case PLSFormatKey::R32UI:
out << "uvec4(pls._" << binding << ",0,0,1)";
break;
default:
UNREACHABLE();
}
}
} // namespace
PLSProgramCache::PLSProgramCache(const FunctionsGL *gl, const gl::Caps &nativeCaps)
: mGL(gl),
mVertexShaderID(mGL->createShader(GL_VERTEX_SHADER)),
mEmptyVAO(CreateVAO(gl)),
mEmptyVAOState(nativeCaps.maxVertexAttributes, nativeCaps.maxVertexAttribBindings),
mCache(MaximumTotalPrograms)
{
// Draws a fullscreen quad from a 4-point GL_TRIANGLE_STRIP.
constexpr char kLoadPLSVertexShader[] = R"(#version 310 es
void main()
{
gl_Position = vec4(mix(vec2(-1), vec2(1), equal(gl_VertexID & ivec2(1, 2), ivec2(0))), 0, 1);
})";
const char *loadPLSVertexShaderPtr = kLoadPLSVertexShader;
mGL->shaderSource(mVertexShaderID, 1, &loadPLSVertexShaderPtr, nullptr);
mGL->compileShader(mVertexShaderID);
ASSERT(IsShaderCompiled(mGL, mVertexShaderID));
}
PLSProgramCache::~PLSProgramCache()
{
mGL->deleteShader(mVertexShaderID);
mGL->deleteVertexArrays(1, &mEmptyVAO);
}
void PLSProgramKeyBuilder::prependPlane(GLenum internalformat, bool preserved)
{
uint64_t rawFormatKey = static_cast<uint64_t>(GetPLSFormatKey(internalformat));
mRawKey = (mRawKey << (PLSProgramKey::BitsPerPlane - 1)) | rawFormatKey;
mRawKey = (mRawKey << 1) | static_cast<uint64_t>(preserved);
}
PLSProgramKey PLSProgramKeyBuilder::finish(PLSProgramType type)
{
return PLSProgramKey((mRawKey << 1) | static_cast<uint64_t>(type));
}
PLSProgramType PLSProgramKey::type() const
{
return static_cast<PLSProgramType>(mRawKey & 1);
}
bool PLSProgramKey::areAnyPreserved() const
{
// The bottom bit of the entire rawKey says whether the program is load or store.
// The bottom bit in each individual plane sub-key says whether the plane is preserved.
constexpr uint64_t PreserveMask = BitRepeat(1, BitsPerPlane) << 1;
return (mRawKey & PreserveMask) != 0;
}
PLSProgramKey::Iter::Iter(const PLSProgramKey &key)
: mPlaneKeys(key.rawKey() >> 1) // The first rawKey bit says if the program is load or store.
{
skipInactivePlanes();
}
PLSFormatKey PLSProgramKey::Iter::formatKey() const
{
return static_cast<PLSFormatKey>((mPlaneKeys & SinglePlaneMask) >> 1);
}
bool PLSProgramKey::Iter::preserved() const
{
return mPlaneKeys & 1;
}
bool PLSProgramKey::Iter::operator!=(const Iter &iter) const
{
return mPlaneKeys != iter.mPlaneKeys;
}
void PLSProgramKey::Iter::operator++()
{
++mBinding;
mPlaneKeys >>= BitsPerPlane;
skipInactivePlanes();
}
void PLSProgramKey::Iter::skipInactivePlanes()
{
// Skip over any planes that are not active. The only effect inactive planes have on shaders is
// to offset the next binding index.
if (mPlaneKeys != 0 && formatKey() == PLSFormatKey::INACTIVE)
{
++*this; // Recurses if there are adjacent inactive planes.
}
}
PLSProgram::PLSProgram(const FunctionsGL *gl, GLuint vertexShaderID, PLSProgramKey key)
: mGL(gl), mKey(key), mProgramID(mGL->createProgram())
{
std::ostringstream fs;
fs << "#version 310 es\n";
fs << "#extension GL_EXT_shader_pixel_local_storage : require\n";
// Emit the global pixel local storage interface block.
fs << GetPLSQualifier(mKey.type()) << " PLS{";
for (auto [binding, formatKey, preserved] : mKey)
{
fs << "layout(" << GetFormatQualifier(formatKey) << ")" << GetFormatPrecision(formatKey)
<< " " << GetFormatRawType(formatKey) << " _" << binding << ";";
}
fs << "}pls;\n";
// Emit the sources/destinations of each PLS plane (either images or uniform clear values).
for (auto [binding, formatKey, preserved] : mKey)
{
if (mKey.type() == PLSProgramType::Load)
{
if (preserved)
{
// Emit an image to load into the PLS plane.
fs << "layout(binding=" << binding << "," << GetFormatQualifier(formatKey)
<< ")uniform readonly " << GetFormatPrecision(formatKey) << " "
<< GetFormatPrefix(formatKey) << "image2D i" << binding << ";";
}
else
{
// Emit a uniform clear value to initialize the PLS plane with.
fs << "uniform " << GetFormatPrecision(formatKey) << " "
<< GetFormatPrefix(formatKey) << "vec4 c" << binding << ";";
}
}
else
{
if (preserved)
{
// Emit an image to dump the PLS plane to.
fs << "layout(binding=" << binding << "," << GetFormatQualifier(formatKey)
<< ")uniform writeonly " << GetFormatPrecision(formatKey) << " "
<< GetFormatPrefix(formatKey) << "image2D i" << binding << ";";
}
}
}
fs << "void main(){";
if (mKey.areAnyPreserved())
{
fs << "highp ivec2 pixelCoord=ivec2(floor(gl_FragCoord.xy));";
}
// Emit the load/store operations for each plane.
for (auto [binding, formatKey, preserved] : mKey)
{
if (mKey.type() == PLSProgramType::Load)
{
fs << "pls._" << binding << "=";
if (preserved)
{
fs << "imageLoad(i" << binding << ",pixelCoord)";
}
else
{
fs << "c" << binding;
}
fs << GetFormatSwizzle(formatKey) << ";";
}
else
{
if (preserved)
{
fs << "imageStore(i" << binding << ",pixelCoord,";
ExpandPLSVar(fs, binding, formatKey);
fs << ");";
}
}
}
fs << "}";
// Compile.
GLuint fragmentShaderID = mGL->createShader(GL_FRAGMENT_SHADER);
std::string str = fs.str();
const char *source = str.c_str();
mGL->shaderSource(fragmentShaderID, 1, &source, nullptr);
mGL->compileShader(fragmentShaderID);
ASSERT(IsShaderCompiled(mGL, fragmentShaderID));
// Link.
mGL->attachShader(mProgramID, vertexShaderID);
mGL->attachShader(mProgramID, fragmentShaderID);
mGL->linkProgram(mProgramID);
ASSERT(IsProgramLinked(mGL, mProgramID));
mGL->deleteShader(fragmentShaderID);
// Get the locations of uniform clear values.
if (mKey.type() == PLSProgramType::Load)
{
for (auto [binding, formatKey, preserved] : mKey)
{
if (!preserved)
{
std::ostringstream name;
name << "c" << binding;
mClearValueUniformLocations[binding] = {
mGL->getUniformLocation(mProgramID, name.str().c_str())};
ASSERT(mClearValueUniformLocations[binding] >= 0);
}
}
}
}
PLSProgram::~PLSProgram()
{
mGL->deleteProgram(mProgramID);
}
void PLSProgram::setClearValues(const gl::PixelLocalStoragePlane planes[],
const GLenum loadops[]) const
{
ASSERT(mKey.type() == PLSProgramType::Load);
// Updates uniforms representing clear values on the current program.
class ClearUniformCommands : public gl::PixelLocalStoragePlane::ClearCommands
{
public:
ClearUniformCommands(const FunctionsGL *gl, const GLint *clearValueUniformLocations)
: mGL(gl), mClearValueUniformLocations(clearValueUniformLocations)
{}
void clearfv(int binding, const GLfloat value[]) const override
{
mGL->uniform4fv(mClearValueUniformLocations[binding], 1, value);
}
void cleariv(int binding, const GLint value[]) const override
{
mGL->uniform4iv(mClearValueUniformLocations[binding], 1, value);
}
void clearuiv(int binding, const GLuint value[]) const override
{
mGL->uniform4uiv(mClearValueUniformLocations[binding], 1, value);
}
private:
const FunctionsGL *const mGL;
const GLint *const mClearValueUniformLocations;
};
ClearUniformCommands clearUniformCommands(mGL, mClearValueUniformLocations.data());
for (auto [binding, formatKey, preserved] : mKey)
{
if (!preserved)
{
planes[binding].issueClearCommand(&clearUniformCommands, binding, loadops[binding]);
}
}
}
const PLSProgram *PLSProgramCache::getProgram(PLSProgramKey key)
{
const std::unique_ptr<PLSProgram> *programPtr;
if (!mCache.get(key.rawKey(), &programPtr))
{
auto program = std::make_unique<PLSProgram>(mGL, mVertexShaderID, key);
programPtr = mCache.put(key.rawKey(), std::move(program), 1);
}
ASSERT(programPtr != nullptr);
return programPtr->get();
}
} // namespace rx