blob: 7933cccc0fbab745f996752e6ec234585f3cf622 [file]
/*
* Copyright 2023 Rive
*/
#include "background_shader_compiler.h"
#include "generated/shaders/metal.glsl.hpp"
#include "generated/shaders/constants.glsl.hpp"
#include "generated/shaders/image_draw_uniforms.glsl.hpp"
#include "generated/shaders/flush_uniforms.glsl.hpp"
#include "generated/shaders/common.glsl.hpp"
#include "generated/shaders/advanced_blend.glsl.hpp"
#include "generated/shaders/draw_path_common.glsl.hpp"
#include "generated/shaders/draw_path.vert.hpp"
#include "generated/shaders/draw_raster_order_path.frag.hpp"
#include "generated/shaders/draw_image_mesh.vert.hpp"
#include "generated/shaders/draw_mesh.frag.hpp"
#ifndef RIVE_IOS
// iOS doesn't need the atomic shaders; every non-simulated iOS device supports
// framebuffer reads.
#include "generated/shaders/atomic_draw.glsl.hpp"
#endif
#include <sstream>
namespace rive::gpu
{
BackgroundShaderCompiler::~BackgroundShaderCompiler()
{
if (m_compilerThread.joinable())
{
{
std::lock_guard lock(m_mutex);
m_shouldQuit = true;
}
m_workAddedCondition.notify_all();
m_compilerThread.join();
}
}
void BackgroundShaderCompiler::pushJob(const BackgroundCompileJob& job)
{
{
std::lock_guard lock(m_mutex);
if (!m_compilerThread.joinable())
{
m_compilerThread =
std::thread(&BackgroundShaderCompiler::threadMain, this);
}
m_pendingJobs.push(std::move(job));
}
m_workAddedCondition.notify_all();
}
bool BackgroundShaderCompiler::popFinishedJob(BackgroundCompileJob* job,
bool wait)
{
std::unique_lock lock(m_mutex);
while (m_finishedJobs.empty())
{
if (!wait)
{
return false;
}
m_workFinishedCondition.wait(lock);
}
*job = std::move(m_finishedJobs.back());
m_finishedJobs.pop_back();
return true;
}
void BackgroundShaderCompiler::threadMain()
{
BackgroundCompileJob job;
std::unique_lock lock(m_mutex);
for (;;)
{
while (m_pendingJobs.empty() && !m_shouldQuit)
{
m_workAddedCondition.wait(lock);
}
if (m_shouldQuit)
{
return;
}
job = std::move(m_pendingJobs.front());
m_pendingJobs.pop();
lock.unlock();
gpu::DrawType drawType = job.drawType;
gpu::ShaderFeatures shaderFeatures = job.shaderFeatures;
gpu::InterlockMode interlockMode = job.interlockMode;
gpu::ShaderMiscFlags shaderMiscFlags = job.shaderMiscFlags;
auto defines = [[NSMutableDictionary alloc] init];
defines[@GLSL_VERTEX] = @"";
defines[@GLSL_FRAGMENT] = @"";
for (size_t i = 0; i < gpu::kShaderFeatureCount; ++i)
{
const auto feature = ShaderFeatures(1 << i);
if (enums::is_flag_set(shaderFeatures, feature))
{
const char* macro = gpu::GetShaderFeatureGLSLName(feature);
defines[[NSString stringWithUTF8String:macro]] = @"1";
}
}
if (interlockMode == gpu::InterlockMode::atomics)
{
// Atomic mode uses device buffers instead of framebuffer fetches.
defines[@GLSL_PLS_IMPL_DEVICE_BUFFER] = @"";
if (m_metalFeatures.atomicBarrierType ==
AtomicBarrierType::rasterOrderGroup)
{
defines[@GLSL_PLS_IMPL_DEVICE_BUFFER_RASTER_ORDERED] = @"";
}
if (enums::is_flag_set(
shaderMiscFlags,
gpu::ShaderMiscFlags::fixedFunctionColorOutput))
{
defines[@GLSL_FIXED_FUNCTION_COLOR_OUTPUT] = @"";
}
}
if (enums::is_flag_set(shaderMiscFlags,
gpu::ShaderMiscFlags::clockwiseFill))
{
defines[@GLSL_CLOCKWISE_FILL] = @"1";
}
auto source =
[[NSMutableString alloc] initWithCString:gpu::glsl::metal
encoding:NSUTF8StringEncoding];
[source appendFormat:@"%s\n%s\n%s\n",
gpu::glsl::constants,
gpu::glsl::flush_uniforms,
gpu::glsl::common];
if (drawType == DrawType::imageRect || drawType == DrawType::imageMesh)
{
[source appendFormat:@"%s\n", gpu::glsl::image_draw_uniforms];
}
if (enums::is_flag_set(shaderFeatures,
ShaderFeatures::ENABLE_ADVANCED_BLEND))
{
[source appendFormat:@"%s\n", gpu::glsl::advanced_blend];
}
switch (drawType)
{
case DrawType::midpointFanPatches:
case DrawType::midpointFanCenterAAPatches:
case DrawType::outerCurvePatches:
// Add baseInstance to the instanceID for path draws.
defines[@GLSL_ENABLE_INSTANCE_INDEX] = @"";
defines[@GLSL_DRAW_PATH] = @"";
break;
case DrawType::interiorTriangulation:
defines[@GLSL_DRAW_INTERIOR_TRIANGLES] = @"";
break;
case DrawType::atlasBlit:
defines[@GLSL_ATLAS_BLIT] = @"1";
break;
case DrawType::imageRect:
#ifdef RIVE_IOS
RIVE_UNREACHABLE();
#else
assert(interlockMode == InterlockMode::atomics);
defines[@GLSL_DRAW_IMAGE] = @"";
defines[@GLSL_DRAW_IMAGE_RECT] = @"";
#endif
break;
case DrawType::imageMesh:
defines[@GLSL_DRAW_IMAGE] = @"";
defines[@GLSL_DRAW_IMAGE_MESH] = @"";
break;
case DrawType::renderPassInitialize:
#ifdef RIVE_IOS
RIVE_UNREACHABLE();
#else
assert(interlockMode == InterlockMode::atomics);
defines[@GLSL_DRAW_RENDER_TARGET_UPDATE_BOUNDS] = @"";
defines[@GLSL_INITIALIZE_PLS] = @"";
if (enums::is_flag_set(shaderMiscFlags,
gpu::ShaderMiscFlags::storeColorClear))
{
// Define this as "true" instead of an empty string because
// it's a specialization constant in some backends and gets
// branched on at runtime.
defines[@GLSL_STORE_COLOR_CLEAR] = @"true";
}
// Metal copies the render target directly to the storage buffer
// instead of seeding it with the shader.
assert(!enums::is_flag_set(
shaderMiscFlags,
gpu::ShaderMiscFlags::loadColorFromDstTexture));
if (enums::is_flag_set(
shaderMiscFlags,
gpu::ShaderMiscFlags::swizzleColorBGRAToRGBA))
{
defines[@GLSL_SWIZZLE_COLOR_BGRA_TO_RGBA] = @"";
}
#endif
break;
case DrawType::renderPassResolve:
#ifdef RIVE_IOS
RIVE_UNREACHABLE();
#else
assert(interlockMode == InterlockMode::atomics);
defines[@GLSL_DRAW_RENDER_TARGET_UPDATE_BOUNDS] = @"";
defines[@GLSL_RESOLVE_PLS] = @"";
if (enums::is_flag_set(
shaderMiscFlags,
gpu::ShaderMiscFlags::coalescedResolveAndTransfer))
{
defines[@GLSL_COALESCED_PLS_RESOLVE_AND_TRANSFER] = @"";
}
#endif
break;
case DrawType::msaaStrokes:
case DrawType::msaaMidpointFanBorrowedCoverage:
case DrawType::msaaMidpointFans:
case DrawType::msaaMidpointFanStencilReset:
case DrawType::msaaMidpointFanPathsStencil:
case DrawType::msaaMidpointFanPathsCover:
case DrawType::msaaOuterCubics:
case DrawType::clipReset:
RIVE_UNREACHABLE();
}
#ifndef RIVE_IOS
if (interlockMode == gpu::InterlockMode::atomics)
{
[source appendFormat:@"%s\n", gpu::glsl::draw_path_common];
[source appendFormat:@"%s\n", gpu::glsl::atomic_draw];
}
else
#endif
{
assert(interlockMode == gpu::InterlockMode::rasterOrdering);
switch (drawType)
{
case DrawType::midpointFanPatches:
case DrawType::midpointFanCenterAAPatches:
case DrawType::outerCurvePatches:
case DrawType::interiorTriangulation:
[source appendFormat:@"%s\n", gpu::glsl::draw_path_common];
[source appendFormat:@"%s\n", gpu::glsl::draw_path_vert];
[source
appendFormat:@"%s\n",
gpu::glsl::draw_raster_order_path_frag];
break;
case DrawType::atlasBlit:
[source appendFormat:@"%s\n", gpu::glsl::draw_path_common];
[source appendFormat:@"%s\n", gpu::glsl::draw_path_vert];
[source appendFormat:@"%s\n", gpu::glsl::draw_mesh_frag];
break;
case DrawType::imageMesh:
[source
appendFormat:@"%s\n", gpu::glsl::draw_image_mesh_vert];
[source appendFormat:@"%s\n", gpu::glsl::draw_mesh_frag];
break;
case DrawType::imageRect:
case DrawType::msaaStrokes:
case DrawType::msaaMidpointFanBorrowedCoverage:
case DrawType::msaaMidpointFans:
case DrawType::msaaMidpointFanStencilReset:
case DrawType::msaaMidpointFanPathsStencil:
case DrawType::msaaMidpointFanPathsCover:
case DrawType::msaaOuterCubics:
case DrawType::clipReset:
case DrawType::renderPassInitialize:
case DrawType::renderPassResolve:
RIVE_UNREACHABLE();
}
}
NSError* err = nil;
MTLCompileOptions* compileOptions = [MTLCompileOptions new];
#if defined(RIVE_IOS) || defined(RIVE_IOS_SIMULATOR)
compileOptions.languageVersion =
MTLLanguageVersion2_2; // On ios, we need version 2.2+
#else
compileOptions.languageVersion =
MTLLanguageVersion2_3; // On mac, we need version 2.3+
#endif
compileOptions.fastMathEnabled = YES;
if (@available(iOS 14, *))
{
compileOptions.preserveInvariance = YES;
}
compileOptions.preprocessorMacros = defines;
#ifdef WITH_RIVE_TOOLS
if (job.synthesizedFailureType ==
SynthesizedFailureType::shaderCompilation)
{
assert(job.compiledLibrary == nil);
}
else
#endif
{
job.compiledLibrary = [m_gpu newLibraryWithSource:source
options:compileOptions
error:&err];
}
lock.lock();
if (err != nil || job.compiledLibrary == nil)
{
#ifdef WITH_RIVE_TOOLS
if (job.synthesizedFailureType ==
SynthesizedFailureType::shaderCompilation)
{
NSLog(@"RIVE: Synthesizing shader compilation failure...");
}
else
#endif
{
// The compile job failed, most likely to external environmental
// factors. Give up on this shader and let the render context
// fall back on an uber shader instead.
int lineNumber = 1;
std::stringstream stream(source.UTF8String);
std::string lineStr;
while (std::getline(stream, lineStr, '\n'))
{
NSLog(@"RIVE: %4i| %s", lineNumber++, lineStr.c_str());
}
NSLog(@"RIVE: Shader compilation error: %@",
err != nil ? err.localizedDescription : @"<nil>");
}
NSLog(@"RIVE: Failed to compile shader.");
assert(false
#ifdef WITH_RIVE_TOOLS
|| job.synthesizedFailureType ==
SynthesizedFailureType::shaderCompilation
#endif
);
}
m_finishedJobs.push_back(std::move(job));
m_workFinishedCondition.notify_all();
}
}
} // namespace rive::gpu