mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-12 00:30:31 +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:
parent
dfcc8e9822
commit
edf8cf4dc6
@ -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];
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user