mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-23 03:32:32 +00:00
Proves that per-pixel sine/cos evaluation avoids phase issues.
Even in PAL mode. But I'd rather not _require_ this as it kind of negates directly-sampled input.
This commit is contained in:
parent
02cea40ffa
commit
3d564d85fd
@ -15,6 +15,22 @@
|
|||||||
#include "BufferingScanTarget.hpp"
|
#include "BufferingScanTarget.hpp"
|
||||||
#include "FIRFilter.hpp"
|
#include "FIRFilter.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Pipelines in use:
|
||||||
|
|
||||||
|
RGB input -> RGB display:
|
||||||
|
just output it.
|
||||||
|
|
||||||
|
RGB input -> angular:
|
||||||
|
Composition in the display colour space (YIQ or YUV), conversion to and from S-Video or composite per output pixel.
|
||||||
|
|
||||||
|
Luminance/Phase -> angular:
|
||||||
|
Composition, conversion per output pixel.
|
||||||
|
|
||||||
|
Luminance -> composite:
|
||||||
|
Composition, conversion per input pixel.
|
||||||
|
*/
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
struct Uniforms {
|
struct Uniforms {
|
||||||
@ -154,9 +170,9 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
|
|||||||
|
|
||||||
// Create a composition texture up front. (TODO: is it worth switching to an 8bpp texture in composite mode?)
|
// Create a composition texture up front. (TODO: is it worth switching to an 8bpp texture in composite mode?)
|
||||||
MTLTextureDescriptor *const textureDescriptor = [MTLTextureDescriptor
|
MTLTextureDescriptor *const textureDescriptor = [MTLTextureDescriptor
|
||||||
texture2DDescriptorWithPixelFormat:MTLPixelFormatRG8Unorm
|
texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
|
||||||
width:2048 // This 'should do'.
|
width:2048 // This 'should do'.
|
||||||
height:NumBufferedLines
|
height:NumBufferedLines // TODO: I want to turn this down _considerably_. A frame and a bit should be sufficient, though probably I'd also want to adjust the buffering scan target to keep most recent data?
|
||||||
mipmapped:NO];
|
mipmapped:NO];
|
||||||
textureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
|
textureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
|
||||||
textureDescriptor.resourceOptions = MTLResourceStorageModePrivate;
|
textureDescriptor.resourceOptions = MTLResourceStorageModePrivate;
|
||||||
@ -395,10 +411,10 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Whether S-Video or composite, apply the same relatively strong filter to colour channels.
|
// Whether S-Video or composite, apply the same relatively strong filter to colour channels.
|
||||||
SignalProcessing::FIRFilter chrominancefilter(15, cyclesPerLine, 0.0f, colourCyclesPerLine * 0.25f);
|
SignalProcessing::FIRFilter chrominancefilter(15, cyclesPerLine, 0.0f, colourCyclesPerLine);
|
||||||
const auto calculatedCoefficients = chrominancefilter.get_coefficients();
|
const auto calculatedCoefficients = chrominancefilter.get_coefficients();
|
||||||
for(size_t c = 0; c < 8; ++c) {
|
for(size_t c = 0; c < 8; ++c) {
|
||||||
firCoefficients[c].y = firCoefficients[c].z = calculatedCoefficients[c] * (isSVideoOutput ? 4.0f : 1.0f);
|
firCoefficients[c].y = firCoefficients[c].z = calculatedCoefficients[c] * (isSVideoOutput ? 2.0f : 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
uniforms()->radiansPerPixel = (colourCyclesPerLine * 3.141592654f * 2.0f) / cyclesPerLine;
|
uniforms()->radiansPerPixel = (colourCyclesPerLine * 3.141592654f * 2.0f) / cyclesPerLine;
|
||||||
|
@ -81,10 +81,9 @@ struct SourceInterpolator {
|
|||||||
float4 position [[position]];
|
float4 position [[position]];
|
||||||
float2 textureCoordinates;
|
float2 textureCoordinates;
|
||||||
float colourPhase;
|
float colourPhase;
|
||||||
float colourAmplitude;
|
float colourAmplitude [[flat]];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Vertex shaders.
|
// MARK: - Vertex shaders.
|
||||||
|
|
||||||
float2 textureLocation(constant Line *line, float offset) {
|
float2 textureLocation(constant Line *line, float offset) {
|
||||||
@ -202,6 +201,10 @@ vertex SourceInterpolator scanToComposition( constant Uniforms &uniforms [[buffe
|
|||||||
|
|
||||||
// MARK: - Various input format conversion samplers.
|
// MARK: - Various input format conversion samplers.
|
||||||
|
|
||||||
|
float2 quadrature(float phase) {
|
||||||
|
return float2(cos(phase), sin(phase));
|
||||||
|
}
|
||||||
|
|
||||||
// There's only one meaningful way to sample the luminance formats.
|
// There's only one meaningful way to sample the luminance formats.
|
||||||
|
|
||||||
fragment float4 sampleLuminance1(SourceInterpolator vert [[stage_in]], texture2d<ushort> texture [[texture(0)]]) {
|
fragment float4 sampleLuminance1(SourceInterpolator vert [[stage_in]], texture2d<ushort> texture [[texture(0)]]) {
|
||||||
@ -276,13 +279,7 @@ float3 convertRed1Green1Blue1(SourceInterpolator vert, texture2d<ushort> texture
|
|||||||
\
|
\
|
||||||
fragment float4 svideoSample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
|
fragment float4 svideoSample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
|
||||||
const auto colour = uniforms.fromRGB * convert##name(vert, texture); \
|
const auto colour = uniforms.fromRGB * convert##name(vert, texture); \
|
||||||
const float2 colourSubcarrier = float2(cos(vert.colourPhase), sin(vert.colourPhase)); \
|
return float4(colour, 1.0); \
|
||||||
return float4( \
|
|
||||||
colour.r, \
|
|
||||||
dot(colour.gb, colourSubcarrier)*0.5 + 0.5, \
|
|
||||||
0.0, \
|
|
||||||
1.0 \
|
|
||||||
); \
|
|
||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
fragment float4 compositeSample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
|
fragment float4 compositeSample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
|
||||||
@ -297,6 +294,14 @@ float3 convertRed1Green1Blue1(SourceInterpolator vert, texture2d<ushort> texture
|
|||||||
); \
|
); \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const float2 colourSubcarrier = float2(cos(vert.colourPhase), sin(vert.colourPhase)); \
|
||||||
|
// return float4( \
|
||||||
|
// colour.r, \
|
||||||
|
// dot(colour.gb, colourSubcarrier)*0.5 + 0.5, \
|
||||||
|
// 0.0, \
|
||||||
|
// 1.0 \
|
||||||
|
// ); \
|
||||||
|
|
||||||
DeclareShaders(Red8Green8Blue8, float)
|
DeclareShaders(Red8Green8Blue8, float)
|
||||||
DeclareShaders(Red4Green4Blue4, ushort)
|
DeclareShaders(Red4Green4Blue4, ushort)
|
||||||
DeclareShaders(Red2Green2Blue2, ushort)
|
DeclareShaders(Red2Green2Blue2, ushort)
|
||||||
@ -340,14 +345,33 @@ fragment float4 clearFragment() {
|
|||||||
// MARK: - Conversion fragment shaders
|
// MARK: - Conversion fragment shaders
|
||||||
|
|
||||||
fragment float4 filterSVideoFragment(SourceInterpolator vert [[stage_in]], texture2d<float> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) {
|
fragment float4 filterSVideoFragment(SourceInterpolator vert [[stage_in]], texture2d<float> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) {
|
||||||
#define Sample(x) texture.sample(standardSampler, vert.textureCoordinates + float2(x, 0.0f)).rg - float2(0.0f, 0.5f)
|
#define Sample(x) texture.sample(standardSampler, vert.textureCoordinates + float2(x, 0.0f))
|
||||||
const float2 rawSamples[] = {
|
float4 rawSamples[] = {
|
||||||
Sample(-7), Sample(-6), Sample(-5), Sample(-4), Sample(-3), Sample(-2), Sample(-1),
|
Sample(-7), Sample(-6), Sample(-5), Sample(-4), Sample(-3), Sample(-2), Sample(-1),
|
||||||
Sample(0),
|
Sample(0),
|
||||||
Sample(1), Sample(2), Sample(3), Sample(4), Sample(5), Sample(6), Sample(7),
|
Sample(1), Sample(2), Sample(3), Sample(4), Sample(5), Sample(6), Sample(7),
|
||||||
};
|
};
|
||||||
#undef Sample
|
#undef Sample
|
||||||
|
|
||||||
|
#define Offset(x) vert.colourPhase + (x)*uniforms.radiansPerPixel
|
||||||
|
const float angles[] = {
|
||||||
|
Offset(-7), Offset(-6), Offset(-5), Offset(-4), Offset(-3), Offset(-2), Offset(-1),
|
||||||
|
vert.colourPhase,
|
||||||
|
Offset(1), Offset(2), Offset(3), Offset(4), Offset(5), Offset(6), Offset(7)
|
||||||
|
};
|
||||||
|
#undef Offset
|
||||||
|
|
||||||
|
#define Map(x) { \
|
||||||
|
const float2 colourSubcarrier = float2(cos(angles[x]), sin(angles[x])); \
|
||||||
|
rawSamples[x].g = dot(rawSamples[x].gb, colourSubcarrier); \
|
||||||
|
}
|
||||||
|
|
||||||
|
Map(0); Map(1); Map(2); Map(3); Map(4); Map(5);
|
||||||
|
Map(6); Map(7); Map(8); Map(9); Map(10); Map(11);
|
||||||
|
Map(12); Map(13); Map(14);
|
||||||
|
|
||||||
|
#undef Map
|
||||||
|
|
||||||
#define Sample(c, o, a) \
|
#define Sample(c, o, a) \
|
||||||
uniforms.firCoefficients[c] * float3(rawSamples[o].r, rawSamples[o].g*cos(vert.colourPhase + (a)*uniforms.radiansPerPixel), rawSamples[o].g*sin(vert.colourPhase + (a)*uniforms.radiansPerPixel))
|
uniforms.firCoefficients[c] * float3(rawSamples[o].r, rawSamples[o].g*cos(vert.colourPhase + (a)*uniforms.radiansPerPixel), rawSamples[o].g*sin(vert.colourPhase + (a)*uniforms.radiansPerPixel))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user