1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-08-16 05:27:43 +00:00

Completes the set of with/without gamma, and ensures correct alpha selection.

Also culls some other repetitive TODOs.
This commit is contained in:
Thomas Harte
2020-09-09 19:28:38 -04:00
parent dfcc8e9822
commit edf8cf4dc6
2 changed files with 108 additions and 70 deletions

View File

@@ -25,8 +25,6 @@
Source data is converted to 32bpp RGB or to composite directly from its input, at output resolution. Source data is converted to 32bpp RGB or to composite directly from its input, at output resolution.
Gamma correction is applied unless the inputs are 1bpp (e.g. Macintosh-style black/white, TTL-style RGB). Gamma correction is applied unless the inputs are 1bpp (e.g. Macintosh-style black/white, TTL-style RGB).
TODO: filtering when the output size is 'small'.
S-Video S-Video
------- -------
@@ -364,8 +362,6 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
} }
- (void)updateSizeBuffersToSize:(CGSize)size { - (void)updateSizeBuffersToSize:(CGSize)size {
// TODO: consider multisampling here? But it seems like you'd need another level of indirection
// in order to maintain an ongoing buffer that supersamples only at the end.
const NSUInteger frameBufferWidth = NSUInteger(size.width * _view.layer.contentsScale); const NSUInteger frameBufferWidth = NSUInteger(size.width * _view.layer.contentsScale);
const NSUInteger frameBufferHeight = NSUInteger(size.height * _view.layer.contentsScale); const NSUInteger frameBufferHeight = NSUInteger(size.height * _view.layer.contentsScale);
@@ -619,7 +615,6 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
_pipeline = isSVideoOutput ? Pipeline::SVideo : Pipeline::CompositeColour; _pipeline = isSVideoOutput ? Pipeline::SVideo : Pipeline::CompositeColour;
} }
// TODO: factor in gamma, which may or may not be a factor (it isn't for 1-bit formats).
struct FragmentSamplerDictionary { struct FragmentSamplerDictionary {
/// Fragment shader that outputs to the composition buffer for composite processing. /// Fragment shader that outputs to the composition buffer for composite processing.
NSString *const compositionComposite; NSString *const compositionComposite;
@@ -636,29 +631,38 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
NSString *const directRGBWithGamma; NSString *const directRGBWithGamma;
}; };
const FragmentSamplerDictionary samplerDictionary[8] = { const FragmentSamplerDictionary samplerDictionary[8] = {
{@"compositeSampleLuminance1", nullptr, @"sampleLuminance1", @"sampleLuminance1", nullptr, nullptr}, // Composite formats.
{@"compositeSampleLuminance8", nullptr, @"sampleLuminance8", nullptr}, {@"compositeSampleLuminance1", nil, @"sampleLuminance1", @"sampleLuminance1"},
{@"compositeSamplePhaseLinkedLuminance8", nullptr, @"samplePhaseLinkedLuminance8", nullptr}, {@"compositeSampleLuminance8", nil, @"sampleLuminance8", @"sampleLuminance8WithGamma"},
{@"compositeSampleLuminance8Phase8", @"sampleLuminance8Phase8", @"compositeSampleLuminance8Phase8", nullptr, nullptr, nullptr}, {@"compositeSamplePhaseLinkedLuminance8", nil, @"samplePhaseLinkedLuminance8", @"samplePhaseLinkedLuminance8WithGamma"},
{@"compositeSampleRed1Green1Blue1", @"svideoSampleRed1Green1Blue1", @"compositeSampleRed1Green1Blue1", nullptr, @"sampleRed1Green1Blue1", nullptr},
{@"compositeSampleRed2Green2Blue2", @"svideoSampleRed2Green2Blue2", @"compositeSampleRed2Green2Blue2", nullptr, @"sampleRed2Green2Blue2", nullptr}, // S-Video formats.
{@"compositeSampleRed4Green4Blue4", @"svideoSampleRed4Green4Blue4", @"compositeSampleRed4Green4Blue4", nullptr, @"sampleRed4Green4Blue4", nullptr}, {@"compositeSampleLuminance8Phase8", @"sampleLuminance8Phase8", @"directCompositeSampleLuminance8Phase8", @"directCompositeSampleLuminance8Phase8WithGamma"},
{@"compositeSampleRed8Green8Blue8", @"svideoSampleRed8Green8Blue8", @"compositeSampleRed8Green8Blue8", nullptr, @"sampleRed8Green8Blue8", nullptr},
// RGB formats.
{@"compositeSampleRed1Green1Blue1", @"svideoSampleRed1Green1Blue1", @"directCompositeSampleRed1Green1Blue1", @"directCompositeSampleRed1Green1Blue1WithGamma", @"sampleRed1Green1Blue1", @"sampleRed1Green1Blue1"},
{@"compositeSampleRed2Green2Blue2", @"svideoSampleRed2Green2Blue2", @"directCompositeSampleRed2Green2Blue2", @"directCompositeSampleRed2Green2Blue2WithGamma", @"sampleRed2Green2Blue2", @"sampleRed2Green2Blue2WithGamma"},
{@"compositeSampleRed4Green4Blue4", @"svideoSampleRed4Green4Blue4", @"directCompositeSampleRed4Green4Blue4", @"directCompositeSampleRed4Green4Blue4WithGamma", @"sampleRed4Green4Blue4", @"sampleRed4Green4Blue4WithGamma"},
{@"compositeSampleRed8Green8Blue8", @"svideoSampleRed8Green8Blue8", @"directCompositeSampleRed8Green8Blue8", @"directCompositeSampleRed8Green8Blue8WithGamma", @"sampleRed8Green8Blue8", @"sampleRed8Green8Blue8WithGamma"},
}; };
#ifndef NDEBUG #ifndef NDEBUG
// Do a quick check of the names entered above. I don't think this is possible at compile time. // Do a quick check that all the shaders named above are defined in the Metal code. I don't think this is possible at compile time.
// for(int c = 0; c < 8; ++c) { for(int c = 0; c < 8; ++c) {
// if(samplerDictionary[c].compositionComposite) assert([library newFunctionWithName:samplerDictionary[c].compositionComposite]); #define Test(x) if(samplerDictionary[c].x) assert([library newFunctionWithName:samplerDictionary[c].x]);
// if(samplerDictionary[c].compositionSVideo) assert([library newFunctionWithName:samplerDictionary[c].compositionSVideo]); Test(compositionComposite);
// if(samplerDictionary[c].directComposite) assert([library newFunctionWithName:samplerDictionary[c].directComposite]); Test(compositionSVideo);
// if(samplerDictionary[c].directRGB) assert([library newFunctionWithName:samplerDictionary[c].directRGB]); Test(directComposite);
// } Test(directCompositeWithGamma);
Test(directRGB);
Test(directRGBWithGamma);
#undef Test
}
#endif #endif
uniforms()->cyclesMultiplier = 1.0f; uniforms()->cyclesMultiplier = 1.0f;
if(_pipeline != Pipeline::DirectToDisplay) { if(_pipeline != Pipeline::DirectToDisplay) {
// Pick a suitable cycle multiplier. TODO: can I reduce this from a multiple of 4? // Pick a suitable cycle multiplier.
const float minimumSize = 4.0f * float(modals.colour_cycle_numerator) / float(modals.colour_cycle_denominator); const float minimumSize = 4.0f * float(modals.colour_cycle_numerator) / float(modals.colour_cycle_denominator);
while(uniforms()->cyclesMultiplier * modals.cycles_per_line < minimumSize) { while(uniforms()->cyclesMultiplier * modals.cycles_per_line < minimumSize) {
uniforms()->cyclesMultiplier += 1.0f; uniforms()->cyclesMultiplier += 1.0f;
@@ -678,7 +682,6 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
// Generate the chrominance filter. // Generate the chrominance filter.
{ {
// auto *const firCoefficients = uniforms()->chromaKernel;
simd::float3 firCoefficients[8]; simd::float3 firCoefficients[8];
const auto chromaCoefficients = boxCoefficients(radiansPerPixel, 3.141592654f); const auto chromaCoefficients = boxCoefficients(radiansPerPixel, 3.141592654f);
_chromaKernelSize = 15; _chromaKernelSize = 15;
@@ -757,8 +760,14 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
pipelineDescriptor.fragmentFunction = [library newFunctionWithName:@"interpolateFragment"]; pipelineDescriptor.fragmentFunction = [library newFunctionWithName:@"interpolateFragment"];
} else { } else {
const bool isRGBOutput = modals.display_type == Outputs::Display::DisplayType::RGB; const bool isRGBOutput = modals.display_type == Outputs::Display::DisplayType::RGB;
pipelineDescriptor.fragmentFunction =
[library newFunctionWithName:isRGBOutput ? samplerDictionary[int(modals.input_data_type)].directRGB : samplerDictionary[int(modals.input_data_type)].directComposite]; NSString *shaderName;
if(isRGBOutput) {
shaderName = [self shouldApplyGamma] ? samplerDictionary[int(modals.input_data_type)].directRGBWithGamma : samplerDictionary[int(modals.input_data_type)].directRGB;
} else {
shaderName = [self shouldApplyGamma] ? samplerDictionary[int(modals.input_data_type)].directCompositeWithGamma : samplerDictionary[int(modals.input_data_type)].directComposite;
}
pipelineDescriptor.fragmentFunction = [library newFunctionWithName:shaderName];
} }
// Enable blending. // Enable blending.
@@ -818,6 +827,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
[encoder setVertexTexture:_frameBuffer atIndex:0]; [encoder setVertexTexture:_frameBuffer atIndex:0];
[encoder setFragmentTexture:_frameBuffer atIndex:0]; [encoder setFragmentTexture:_frameBuffer atIndex:0];
[encoder setFragmentBuffer:_uniformsBuffer offset:0 atIndex:0];
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; [encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
[encoder endEncoding]; [encoder endEncoding];

View File

@@ -10,9 +10,6 @@
using namespace metal; using namespace metal;
// TODO: I'm being very loose, so far, in use of alpha. Sometimes it's 0.64, somtimes its 1.0.
// Apply some rigour, for crying out loud.
struct Uniforms { struct Uniforms {
// This is used to scale scan positions, i.e. it provides the range // This is used to scale scan positions, i.e. it provides the range
// for mapping from scan-style integer positions into eye space. // for mapping from scan-style integer positions into eye space.
@@ -262,35 +259,42 @@ half4 composite(half level, half2 quadrature, half amplitude) {
// composite format used for composition. Direct sampling is always for final output, so the two // composite format used for composition. Direct sampling is always for final output, so the two
// 8-bit formats also provide a gamma option. // 8-bit formats also provide a gamma option.
fragment half4 sampleLuminance1(SourceInterpolator vert [[stage_in]], texture2d<ushort> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { half convertLuminance1(SourceInterpolator vert [[stage_in]], texture2d<ushort> texture [[texture(0)]]) {
const half luminance = clamp(half(texture.sample(standardSampler, vert.textureCoordinates).r), half(0.0f), half(1.0f)) * uniforms.outputMultiplier; return clamp(half(texture.sample(standardSampler, vert.textureCoordinates).r), half(0.0f), half(1.0f));
return half4(half3(luminance), uniforms.outputAlpha);
} }
fragment half4 compositeSampleLuminance1(SourceInterpolator vert [[stage_in]], texture2d<ushort> texture [[texture(0)]]) { half convertLuminance8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
return composite(texture.sample(standardSampler, vert.textureCoordinates).r, quadrature(vert.colourPhase), vert.colourAmplitude); return texture.sample(standardSampler, vert.textureCoordinates).r;
} }
fragment half4 sampleLuminance8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { half convertPhaseLinkedLuminance8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
return half4(texture.sample(standardSampler, vert.textureCoordinates).rrr * uniforms.outputMultiplier, uniforms.outputAlpha);
}
fragment half4 compositeSampleLuminance8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
return composite(texture.sample(standardSampler, vert.textureCoordinates).r, quadrature(vert.colourPhase), vert.colourAmplitude);
}
fragment half4 samplePhaseLinkedLuminance8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) {
const int offset = int(vert.unitColourPhase * 4.0f) & 3; const int offset = int(vert.unitColourPhase * 4.0f) & 3;
auto sample = texture.sample(standardSampler, vert.textureCoordinates); auto sample = texture.sample(standardSampler, vert.textureCoordinates);
return half4(half3(sample[offset]), uniforms.outputAlpha); return sample[offset];
} }
fragment half4 compositeSamplePhaseLinkedLuminance8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
const int offset = int(vert.unitColourPhase * 4.0f) & 3; #define CompositeSet(name, type) \
const float snappedColourPhase = float(offset) * (0.5f * 3.141592654f); // TODO: plus machine-supplied offset. fragment half4 sample##name(SourceInterpolator vert [[stage_in]], texture2d<type> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
auto sample = texture.sample(standardSampler, vert.textureCoordinates); const half luminance = convert##name(vert, texture) * uniforms.outputMultiplier; \
return composite(sample[offset], quadrature(snappedColourPhase), vert.colourAmplitude); return half4(half3(luminance), uniforms.outputAlpha); \
} } \
\
fragment half4 sample##name##WithGamma(SourceInterpolator vert [[stage_in]], texture2d<type> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
const half luminance = pow(convert##name(vert, texture) * uniforms.outputMultiplier, uniforms.outputGamma); \
return half4(half3(luminance), uniforms.outputAlpha); \
} \
\
fragment half4 compositeSample##name(SourceInterpolator vert [[stage_in]], texture2d<type> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
const half luminance = convert##name(vert, texture) * uniforms.outputMultiplier; \
return composite(luminance, quadrature(vert.colourPhase), vert.colourAmplitude); \
}
CompositeSet(Luminance1, ushort);
CompositeSet(Luminance8, half);
CompositeSet(PhaseLinkedLuminance8, half);
#undef CompositeSet
// The luminance/phase format can produce either composite or S-Video. // The luminance/phase format can produce either composite or S-Video.
@@ -302,54 +306,65 @@ half2 convertLuminance8Phase8(SourceInterpolator vert [[stage_in]], texture2d<ha
return half2(luminancePhase.r, rawChroma); return half2(luminancePhase.r, rawChroma);
} }
fragment half4 compositeSampleLuminance8Phase8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
const half2 luminanceChroma = convertLuminance8Phase8(vert, texture);
const half luminance = mix(luminanceChroma.r, luminanceChroma.g, vert.colourAmplitude);
return composite(luminance, quadrature(vert.colourPhase), vert.colourAmplitude);
}
fragment half4 sampleLuminance8Phase8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) { fragment half4 sampleLuminance8Phase8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
const half2 luminanceChroma = convertLuminance8Phase8(vert, texture); const half2 luminanceChroma = convertLuminance8Phase8(vert, texture);
const half2 qam = quadrature(vert.colourPhase) * 0.5f; const half2 qam = quadrature(vert.colourPhase) * half(0.5f);
return half4(luminanceChroma.r, return half4(luminanceChroma.r,
half2(0.5f) + luminanceChroma.g*qam, half2(0.5f) + luminanceChroma.g*qam,
half(1.0f)); half(1.0f));
} }
fragment half4 compositeSampleLuminance8Phase8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) { fragment half4 directCompositeSampleLuminance8Phase8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) {
const half2 luminanceChroma = convertLuminance8Phase8(vert, texture); const half2 luminanceChroma = convertLuminance8Phase8(vert, texture);
const half level = mix(luminanceChroma.r, luminanceChroma.g, half(vert.colourAmplitude)); const half luminance = mix(luminanceChroma.r * uniforms.outputMultiplier, luminanceChroma.g, vert.colourAmplitude);
return composite(level, quadrature(vert.colourPhase), vert.colourAmplitude); return half4(half3(luminance), uniforms.outputAlpha);
} }
// All the RGB formats can produce RGB, composite or S-Video. fragment half4 directCompositeSampleLuminance8Phase8WithGamma(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) {
// const half2 luminanceChroma = convertLuminance8Phase8(vert, texture);
// Note on the below: in Metal you may not call a fragment function (so e.g. svideoSampleX can't just cann sampleX). const half luminance = mix(pow(luminanceChroma.r * uniforms.outputMultiplier, uniforms.outputGamma), luminanceChroma.g, vert.colourAmplitude);
// Also I can find no functioning way to offer a templated fragment function. So I don't currently know how return half4(half3(luminance), uniforms.outputAlpha);
// I could avoid the macro mess below. }
// All the RGB formats can produce RGB, composite or S-Video.
// TODO: is the calling convention here causing `vert` and `texture` to be copied?
half3 convertRed8Green8Blue8(SourceInterpolator vert, texture2d<half> texture) { half3 convertRed8Green8Blue8(SourceInterpolator vert, texture2d<half> texture) {
return texture.sample(standardSampler, vert.textureCoordinates).rgb; return texture.sample(standardSampler, vert.textureCoordinates).rgb;
} }
half3 convertRed4Green4Blue4(SourceInterpolator vert, texture2d<ushort> texture) { half3 convertRed4Green4Blue4(SourceInterpolator vert, texture2d<ushort> texture) {
const auto sample = texture.sample(standardSampler, vert.textureCoordinates).rg; const auto sample = texture.sample(standardSampler, vert.textureCoordinates).rg;
return half3(sample.r&15, (sample.g >> 4)&15, sample.g&15); return clamp(half3(sample.r&15, (sample.g >> 4)&15, sample.g&15), half(0.0f), half(1.0f));
} }
half3 convertRed2Green2Blue2(SourceInterpolator vert, texture2d<ushort> texture) { half3 convertRed2Green2Blue2(SourceInterpolator vert, texture2d<ushort> texture) {
const auto sample = texture.sample(standardSampler, vert.textureCoordinates).r; const auto sample = texture.sample(standardSampler, vert.textureCoordinates).r;
return half3((sample >> 4)&3, (sample >> 2)&3, sample&3); return clamp(half3((sample >> 4)&3, (sample >> 2)&3, sample&3), half(0.0f), half(1.0f));
} }
half3 convertRed1Green1Blue1(SourceInterpolator vert, texture2d<ushort> texture) { half3 convertRed1Green1Blue1(SourceInterpolator vert, texture2d<ushort> texture) {
const auto sample = texture.sample(standardSampler, vert.textureCoordinates).r; const auto sample = texture.sample(standardSampler, vert.textureCoordinates).r;
return half3(sample&4, sample&2, sample&1); return clamp(half3(sample&4, sample&2, sample&1), half(0.0f), half(1.0f));
} }
// TODO: don't hard code the 0.64 in sample##name.
#define DeclareShaders(name, pixelType) \ #define DeclareShaders(name, pixelType) \
fragment half4 sample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]]) { \ fragment half4 sample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
return half4(convert##name(vert, texture), 0.64); \ return half4(convert##name(vert, texture), uniforms.outputAlpha); \
} \
\
fragment half4 sample##name##WithGamma(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
return half4(pow(convert##name(vert, texture), uniforms.outputGamma), uniforms.outputAlpha); \
} \ } \
\ \
fragment half4 svideoSample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \ fragment half4 svideoSample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
const auto colour = uniforms.fromRGB * clamp(convert##name(vert, texture), half(0.0f), half(1.0f)); \ const auto colour = uniforms.fromRGB * convert##name(vert, texture); \
const half2 qam = quadrature(vert.colourPhase); \ const half2 qam = quadrature(vert.colourPhase); \
const half chroma = dot(colour.gb, qam); \ const half chroma = dot(colour.gb, qam); \
return half4( \ return half4( \
@@ -359,11 +374,24 @@ half3 convertRed1Green1Blue1(SourceInterpolator vert, texture2d<ushort> texture)
); \ ); \
} \ } \
\ \
half composite##name(SourceInterpolator vert, texture2d<pixelType> texture, constant Uniforms &uniforms, half2 colourSubcarrier) { \
const auto colour = uniforms.fromRGB * convert##name(vert, texture); \
return mix(colour.r, dot(colour.gb, colourSubcarrier), half(vert.colourAmplitude)); \
} \
\
fragment half4 compositeSample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \ fragment half4 compositeSample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
const auto colour = uniforms.fromRGB * clamp(convert##name(vert, texture), half3(0.0f), half3(1.0f)); \
const half2 colourSubcarrier = quadrature(vert.colourPhase); \ const half2 colourSubcarrier = quadrature(vert.colourPhase); \
const half level = mix(colour.r, dot(colour.gb, colourSubcarrier), half(vert.colourAmplitude)); \ return composite(composite##name(vert, texture, uniforms, colourSubcarrier), colourSubcarrier, vert.colourAmplitude); \
return composite(level, colourSubcarrier, vert.colourAmplitude); \ } \
\
fragment half4 directCompositeSample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
const half level = composite##name(vert, texture, uniforms, quadrature(vert.colourPhase)); \
return half4(half3(level), uniforms.outputAlpha); \
} \
\
fragment half4 directCompositeSample##name##WithGamma(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
const half level = pow(composite##name(vert, texture, uniforms, quadrature(vert.colourPhase)), uniforms.outputGamma); \
return half4(half3(level), uniforms.outputAlpha); \
} }
DeclareShaders(Red8Green8Blue8, half) DeclareShaders(Red8Green8Blue8, half)
@@ -379,8 +407,8 @@ fragment half4 interpolateFragment(CopyInterpolator vert [[stage_in]], texture2d
return texture.sample(linearSampler, vert.textureCoordinates); return texture.sample(linearSampler, vert.textureCoordinates);
} }
fragment half4 clearFragment() { fragment half4 clearFragment(constant Uniforms &uniforms [[buffer(0)]]) {
return half4(0.0, 0.0, 0.0, 0.64); return half4(0.0, 0.0, 0.0, uniforms.outputAlpha);
} }
// MARK: - Compute kernels // MARK: - Compute kernels