1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-29 00:29:34 +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.
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
-------
@ -364,8 +362,6 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
}
- (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 frameBufferHeight = NSUInteger(size.height * _view.layer.contentsScale);
@ -619,7 +615,6 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
_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 {
/// Fragment shader that outputs to the composition buffer for composite processing.
NSString *const compositionComposite;
@ -636,29 +631,38 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
NSString *const directRGBWithGamma;
};
const FragmentSamplerDictionary samplerDictionary[8] = {
{@"compositeSampleLuminance1", nullptr, @"sampleLuminance1", @"sampleLuminance1", nullptr, nullptr},
{@"compositeSampleLuminance8", nullptr, @"sampleLuminance8", nullptr},
{@"compositeSamplePhaseLinkedLuminance8", nullptr, @"samplePhaseLinkedLuminance8", nullptr},
{@"compositeSampleLuminance8Phase8", @"sampleLuminance8Phase8", @"compositeSampleLuminance8Phase8", nullptr, nullptr, nullptr},
{@"compositeSampleRed1Green1Blue1", @"svideoSampleRed1Green1Blue1", @"compositeSampleRed1Green1Blue1", nullptr, @"sampleRed1Green1Blue1", nullptr},
{@"compositeSampleRed2Green2Blue2", @"svideoSampleRed2Green2Blue2", @"compositeSampleRed2Green2Blue2", nullptr, @"sampleRed2Green2Blue2", nullptr},
{@"compositeSampleRed4Green4Blue4", @"svideoSampleRed4Green4Blue4", @"compositeSampleRed4Green4Blue4", nullptr, @"sampleRed4Green4Blue4", nullptr},
{@"compositeSampleRed8Green8Blue8", @"svideoSampleRed8Green8Blue8", @"compositeSampleRed8Green8Blue8", nullptr, @"sampleRed8Green8Blue8", nullptr},
// Composite formats.
{@"compositeSampleLuminance1", nil, @"sampleLuminance1", @"sampleLuminance1"},
{@"compositeSampleLuminance8", nil, @"sampleLuminance8", @"sampleLuminance8WithGamma"},
{@"compositeSamplePhaseLinkedLuminance8", nil, @"samplePhaseLinkedLuminance8", @"samplePhaseLinkedLuminance8WithGamma"},
// S-Video formats.
{@"compositeSampleLuminance8Phase8", @"sampleLuminance8Phase8", @"directCompositeSampleLuminance8Phase8", @"directCompositeSampleLuminance8Phase8WithGamma"},
// 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
// Do a quick check of the names entered above. I don't think this is possible at compile time.
// for(int c = 0; c < 8; ++c) {
// if(samplerDictionary[c].compositionComposite) assert([library newFunctionWithName:samplerDictionary[c].compositionComposite]);
// if(samplerDictionary[c].compositionSVideo) assert([library newFunctionWithName:samplerDictionary[c].compositionSVideo]);
// if(samplerDictionary[c].directComposite) assert([library newFunctionWithName:samplerDictionary[c].directComposite]);
// if(samplerDictionary[c].directRGB) assert([library newFunctionWithName:samplerDictionary[c].directRGB]);
// }
// 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) {
#define Test(x) if(samplerDictionary[c].x) assert([library newFunctionWithName:samplerDictionary[c].x]);
Test(compositionComposite);
Test(compositionSVideo);
Test(directComposite);
Test(directCompositeWithGamma);
Test(directRGB);
Test(directRGBWithGamma);
#undef Test
}
#endif
uniforms()->cyclesMultiplier = 1.0f;
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);
while(uniforms()->cyclesMultiplier * modals.cycles_per_line < minimumSize) {
uniforms()->cyclesMultiplier += 1.0f;
@ -678,7 +682,6 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
// Generate the chrominance filter.
{
// auto *const firCoefficients = uniforms()->chromaKernel;
simd::float3 firCoefficients[8];
const auto chromaCoefficients = boxCoefficients(radiansPerPixel, 3.141592654f);
_chromaKernelSize = 15;
@ -757,8 +760,14 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
pipelineDescriptor.fragmentFunction = [library newFunctionWithName:@"interpolateFragment"];
} else {
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.
@ -818,6 +827,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
[encoder setVertexTexture:_frameBuffer atIndex:0];
[encoder setFragmentTexture:_frameBuffer atIndex:0];
[encoder setFragmentBuffer:_uniformsBuffer offset:0 atIndex:0];
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
[encoder endEncoding];

View File

@ -10,9 +10,6 @@
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 {
// This is used to scale scan positions, i.e. it provides the range
// 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
// 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)]]) {
const half luminance = clamp(half(texture.sample(standardSampler, vert.textureCoordinates).r), half(0.0f), half(1.0f)) * uniforms.outputMultiplier;
return half4(half3(luminance), uniforms.outputAlpha);
half convertLuminance1(SourceInterpolator vert [[stage_in]], texture2d<ushort> texture [[texture(0)]]) {
return clamp(half(texture.sample(standardSampler, vert.textureCoordinates).r), half(0.0f), half(1.0f));
}
fragment half4 compositeSampleLuminance1(SourceInterpolator vert [[stage_in]], texture2d<ushort> texture [[texture(0)]]) {
return composite(texture.sample(standardSampler, vert.textureCoordinates).r, quadrature(vert.colourPhase), vert.colourAmplitude);
half convertLuminance8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
return texture.sample(standardSampler, vert.textureCoordinates).r;
}
fragment half4 sampleLuminance8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(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)]]) {
half convertPhaseLinkedLuminance8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
const int offset = int(vert.unitColourPhase * 4.0f) & 3;
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;
const float snappedColourPhase = float(offset) * (0.5f * 3.141592654f); // TODO: plus machine-supplied offset.
auto sample = texture.sample(standardSampler, vert.textureCoordinates);
return composite(sample[offset], quadrature(snappedColourPhase), vert.colourAmplitude);
}
#define CompositeSet(name, type) \
fragment half4 sample##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 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.
@ -302,54 +306,65 @@ half2 convertLuminance8Phase8(SourceInterpolator vert [[stage_in]], texture2d<ha
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)]]) {
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,
half2(0.5f) + luminanceChroma.g*qam,
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 half level = mix(luminanceChroma.r, luminanceChroma.g, half(vert.colourAmplitude));
return composite(level, quadrature(vert.colourPhase), vert.colourAmplitude);
const half luminance = mix(luminanceChroma.r * uniforms.outputMultiplier, luminanceChroma.g, vert.colourAmplitude);
return half4(half3(luminance), uniforms.outputAlpha);
}
// All the RGB formats can produce RGB, composite or S-Video.
//
// Note on the below: in Metal you may not call a fragment function (so e.g. svideoSampleX can't just cann sampleX).
// Also I can find no functioning way to offer a templated fragment function. So I don't currently know how
// I could avoid the macro mess below.
fragment half4 directCompositeSampleLuminance8Phase8WithGamma(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) {
const half2 luminanceChroma = convertLuminance8Phase8(vert, texture);
const half luminance = mix(pow(luminanceChroma.r * uniforms.outputMultiplier, uniforms.outputGamma), luminanceChroma.g, vert.colourAmplitude);
return half4(half3(luminance), uniforms.outputAlpha);
}
// 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) {
return texture.sample(standardSampler, vert.textureCoordinates).rgb;
}
half3 convertRed4Green4Blue4(SourceInterpolator vert, texture2d<ushort> texture) {
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) {
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) {
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) \
fragment half4 sample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]]) { \
return half4(convert##name(vert, texture), 0.64); \
fragment half4 sample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
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)]]) { \
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 half chroma = dot(colour.gb, qam); \
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)]]) { \
const auto colour = uniforms.fromRGB * clamp(convert##name(vert, texture), half3(0.0f), half3(1.0f)); \
const half2 colourSubcarrier = quadrature(vert.colourPhase); \
const half level = mix(colour.r, dot(colour.gb, colourSubcarrier), half(vert.colourAmplitude)); \
return composite(level, colourSubcarrier, vert.colourAmplitude); \
return composite(composite##name(vert, texture, uniforms, colourSubcarrier), 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)
@ -379,8 +407,8 @@ fragment half4 interpolateFragment(CopyInterpolator vert [[stage_in]], texture2d
return texture.sample(linearSampler, vert.textureCoordinates);
}
fragment half4 clearFragment() {
return half4(0.0, 0.0, 0.0, 0.64);
fragment half4 clearFragment(constant Uniforms &uniforms [[buffer(0)]]) {
return half4(0.0, 0.0, 0.0, uniforms.outputAlpha);
}
// MARK: - Compute kernels