mirror of
https://github.com/TomHarte/CLK.git
synced 2024-10-18 23:23:56 +00:00
Takes a shot at adding RGB -> S-Video and composite conversion, for all RGB types.
This commit is contained in:
parent
637ec35d6a
commit
a136a00a2f
@ -18,6 +18,8 @@ struct Uniforms {
|
|||||||
int32_t scale[2];
|
int32_t scale[2];
|
||||||
float lineWidth;
|
float lineWidth;
|
||||||
float aspectRatioMultiplier;
|
float aspectRatioMultiplier;
|
||||||
|
simd::float3x3 toRGB;
|
||||||
|
simd::float3x3 fromRGB;
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr size_t NumBufferedScans = 2048;
|
constexpr size_t NumBufferedScans = 2048;
|
||||||
@ -104,24 +106,36 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
|
|||||||
uniforms()->aspectRatioMultiplier = float((4.0 / 3.0) / (size.width / size.height));
|
uniforms()->aspectRatioMultiplier = float((4.0 / 3.0) / (size.width / size.height));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
- (void)setModals:(const Outputs::Display::ScanTarget::Modals &)modals view:(nonnull MTKView *)view {
|
||||||
@method drawInMTKView:
|
//
|
||||||
@abstract Called on the delegate when it is asked to render into the view
|
// Populate uniforms.
|
||||||
@discussion Called on the delegate when it is asked to render into the view
|
//
|
||||||
*/
|
uniforms()->scale[0] = modals.output_scale.x;
|
||||||
- (void)drawInMTKView:(nonnull MTKView *)view {
|
uniforms()->scale[1] = modals.output_scale.y;
|
||||||
const Outputs::Display::ScanTarget::Modals *const newModals = _scanTarget.new_modals();
|
uniforms()->lineWidth = 1.0f / modals.expected_vertical_lines;
|
||||||
if(newModals) {
|
|
||||||
uniforms()->scale[0] = newModals->output_scale.x;
|
|
||||||
uniforms()->scale[1] = newModals->output_scale.y;
|
|
||||||
uniforms()->lineWidth = 1.0f / newModals->expected_vertical_lines;
|
|
||||||
|
|
||||||
// TODO: obey the rest of the modals generally.
|
const auto toRGB = to_rgb_matrix(modals.composite_colour_space);
|
||||||
|
uniforms()->toRGB = simd::float3x3(
|
||||||
|
simd::float3{toRGB[0], toRGB[1], toRGB[2]},
|
||||||
|
simd::float3{toRGB[3], toRGB[4], toRGB[5]},
|
||||||
|
simd::float3{toRGB[6], toRGB[7], toRGB[8]}
|
||||||
|
);
|
||||||
|
|
||||||
// Generate the appropriate input texture.
|
const auto fromRGB = from_rgb_matrix(modals.composite_colour_space);
|
||||||
|
uniforms()->fromRGB = simd::float3x3(
|
||||||
|
simd::float3{fromRGB[0], fromRGB[1], fromRGB[2]},
|
||||||
|
simd::float3{fromRGB[3], fromRGB[4], fromRGB[5]},
|
||||||
|
simd::float3{fromRGB[6], fromRGB[7], fromRGB[8]}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Generate input texture.
|
||||||
|
//
|
||||||
MTLPixelFormat pixelFormat;
|
MTLPixelFormat pixelFormat;
|
||||||
_bytesPerInputPixel = size_for_data_type(newModals->input_data_type);
|
_bytesPerInputPixel = size_for_data_type(modals.input_data_type);
|
||||||
if(data_type_is_normalised(newModals->input_data_type)) {
|
if(data_type_is_normalised(modals.input_data_type)) {
|
||||||
switch(_bytesPerInputPixel) {
|
switch(_bytesPerInputPixel) {
|
||||||
default:
|
default:
|
||||||
case 1: pixelFormat = MTLPixelFormatR8Unorm; break;
|
case 1: pixelFormat = MTLPixelFormatR8Unorm; break;
|
||||||
@ -151,14 +165,18 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
|
|||||||
bytesPerRow:bytesPerRow];
|
bytesPerRow:bytesPerRow];
|
||||||
_totalTextureBytes = bytesPerRow * BufferingScanTarget::WriteAreaHeight;
|
_totalTextureBytes = bytesPerRow * BufferingScanTarget::WriteAreaHeight;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
// Generate pipeline.
|
// Generate pipeline.
|
||||||
|
//
|
||||||
id<MTLLibrary> library = [view.device newDefaultLibrary];
|
id<MTLLibrary> library = [view.device newDefaultLibrary];
|
||||||
MTLRenderPipelineDescriptor *pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
|
MTLRenderPipelineDescriptor *pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
|
||||||
pipelineDescriptor.colorAttachments[0].pixelFormat = view.colorPixelFormat;
|
pipelineDescriptor.colorAttachments[0].pixelFormat = view.colorPixelFormat;
|
||||||
|
|
||||||
// TODO: logic somewhat more complicated than this, probably
|
// TODO: logic somewhat more complicated than this, probably
|
||||||
pipelineDescriptor.vertexFunction = [library newFunctionWithName:@"scanToDisplay"];
|
pipelineDescriptor.vertexFunction = [library newFunctionWithName:@"scanToDisplay"];
|
||||||
switch(newModals->input_data_type) {
|
switch(modals.input_data_type) {
|
||||||
case Outputs::Display::InputDataType::Luminance1:
|
case Outputs::Display::InputDataType::Luminance1:
|
||||||
pipelineDescriptor.fragmentFunction = [library newFunctionWithName:@"sampleLuminance1"];
|
pipelineDescriptor.fragmentFunction = [library newFunctionWithName:@"sampleLuminance1"];
|
||||||
break;
|
break;
|
||||||
@ -188,6 +206,17 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
|
|||||||
}
|
}
|
||||||
|
|
||||||
_scanPipeline = [view.device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:nil];
|
_scanPipeline = [view.device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method drawInMTKView:
|
||||||
|
@abstract Called on the delegate when it is asked to render into the view
|
||||||
|
@discussion Called on the delegate when it is asked to render into the view
|
||||||
|
*/
|
||||||
|
- (void)drawInMTKView:(nonnull MTKView *)view {
|
||||||
|
const Outputs::Display::ScanTarget::Modals *const newModals = _scanTarget.new_modals();
|
||||||
|
if(newModals) {
|
||||||
|
[self setModals:*newModals view:view];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a command encoder for the view.
|
// Generate a command encoder for the view.
|
||||||
@ -201,6 +230,7 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
|
|||||||
[encoder setFragmentTexture:_writeAreaTexture atIndex:0];
|
[encoder setFragmentTexture:_writeAreaTexture atIndex:0];
|
||||||
[encoder setVertexBuffer:_scansBuffer offset:0 atIndex:0];
|
[encoder setVertexBuffer:_scansBuffer offset:0 atIndex:0];
|
||||||
[encoder setVertexBuffer:_uniformsBuffer offset:0 atIndex:1];
|
[encoder setVertexBuffer:_uniformsBuffer offset:0 atIndex:1];
|
||||||
|
[encoder setFragmentBuffer:_uniformsBuffer offset:0 atIndex:0];
|
||||||
|
|
||||||
_scanTarget.perform([=] (const BufferingScanTarget::OutputArea &outputArea) {
|
_scanTarget.perform([=] (const BufferingScanTarget::OutputArea &outputArea) {
|
||||||
// Ensure texture changes are noted.
|
// Ensure texture changes are noted.
|
||||||
|
@ -19,6 +19,10 @@ struct Uniforms {
|
|||||||
|
|
||||||
// Provides a scaling factor in order to preserve 4:3 central content.
|
// Provides a scaling factor in order to preserve 4:3 central content.
|
||||||
float aspectRatioMultiplier;
|
float aspectRatioMultiplier;
|
||||||
|
|
||||||
|
// Provides conversions to and from RGB for the active colour space.
|
||||||
|
float3x3 toRGB;
|
||||||
|
float3x3 fromRGB;
|
||||||
};
|
};
|
||||||
|
|
||||||
// MARK: - Structs used for receiving data from the emulation.
|
// MARK: - Structs used for receiving data from the emulation.
|
||||||
@ -143,38 +147,56 @@ fragment float4 compositeSampleLuminance8Phase8(SourceInterpolator vert [[stage_
|
|||||||
|
|
||||||
// All the RGB formats can produce RGB, composite or S-Video.
|
// All the RGB formats can produce RGB, composite or S-Video.
|
||||||
//
|
//
|
||||||
// Note on the below: in Metal you may not call a fragment function. Also I can find no
|
// Note on the below: in Metal you may not call a fragment function (so e.g. svideoSampleX can't just cann sampleX).
|
||||||
// functioning way to offer a templated fragment function. So I don't currently know how
|
// Also I can find no functioning way to offer a templated fragment function. So I don't currently know how
|
||||||
// I would avoid the mess below.
|
// I could avoid the macro mess below.
|
||||||
|
|
||||||
|
// TODO: is the calling convention here causing `vert` and `texture` to be copied?
|
||||||
float3 convertRed8Green8Blue8(SourceInterpolator vert, texture2d<float> texture) {
|
float3 convertRed8Green8Blue8(SourceInterpolator vert, texture2d<float> texture) {
|
||||||
return float3(texture.sample(standardSampler, vert.textureCoordinates));
|
return float3(texture.sample(standardSampler, vert.textureCoordinates));
|
||||||
}
|
}
|
||||||
|
|
||||||
#define DeclareShaders(name) \
|
float3 convertRed4Green4Blue4(SourceInterpolator vert, texture2d<ushort> texture) {
|
||||||
fragment float4 sample##name(SourceInterpolator vert [[stage_in]], texture2d<float> texture [[texture(0)]]) { \
|
const auto sample = texture.sample(standardSampler, vert.textureCoordinates).rg;
|
||||||
|
return float3(sample.r&15, (sample.g >> 4)&15, sample.g&15);
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 convertRed2Green2Blue2(SourceInterpolator vert, texture2d<ushort> texture) {
|
||||||
|
const auto sample = texture.sample(standardSampler, vert.textureCoordinates).r;
|
||||||
|
return float3((sample >> 4)&3, (sample >> 2)&3, sample&3);
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 convertRed1Green1Blue1(SourceInterpolator vert, texture2d<ushort> texture) {
|
||||||
|
const auto sample = texture.sample(standardSampler, vert.textureCoordinates).r;
|
||||||
|
return float3(sample&4, sample&2, sample&1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DeclareShaders(name, pixelType) \
|
||||||
|
fragment float4 sample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]]) { \
|
||||||
return float4(convert##name(vert, texture), 1.0); \
|
return float4(convert##name(vert, texture), 1.0); \
|
||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
fragment float4 svideoSample##name(SourceInterpolator vert [[stage_in]], texture2d<float> texture [[texture(0)]]) { \
|
fragment float4 svideoSample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
|
||||||
const auto colour = convert##name(vert, texture); \
|
const auto colour = uniforms.fromRGB * convert##name(vert, texture); \
|
||||||
return float4(colour, 1.0); \
|
const float2 colourSubcarrier = float2(sin(vert.colourPhase), cos(vert.colourPhase))*0.5 + float2(0.5); \
|
||||||
|
return float4( \
|
||||||
|
colour.r, \
|
||||||
|
dot(colour.gb, colourSubcarrier), \
|
||||||
|
0.0, \
|
||||||
|
1.0 \
|
||||||
|
); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
fragment float4 compositeSample##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 float2 colourSubcarrier = float2(sin(vert.colourPhase), cos(vert.colourPhase)); \
|
||||||
|
return float4( \
|
||||||
|
float3(mix(colour.r, dot(colour.gb, colourSubcarrier), vert.colourAmplitude)), \
|
||||||
|
1.0 \
|
||||||
|
); \
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: a colour-space conversion matrix is required to proceed.
|
DeclareShaders(Red8Green8Blue8, float)
|
||||||
DeclareShaders(Red8Green8Blue8)
|
DeclareShaders(Red4Green4Blue4, ushort)
|
||||||
|
DeclareShaders(Red2Green2Blue2, ushort)
|
||||||
fragment float4 sampleRed1Green1Blue1(SourceInterpolator vert [[stage_in]], texture2d<ushort> texture [[texture(0)]]) {
|
DeclareShaders(Red1Green1Blue1, ushort)
|
||||||
const auto sample = texture.sample(standardSampler, vert.textureCoordinates).r;
|
|
||||||
return float4(sample&4, sample&2, sample&1, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment float4 sampleRed2Green2Blue2(SourceInterpolator vert [[stage_in]], texture2d<ushort> texture [[texture(0)]]) {
|
|
||||||
const auto sample = texture.sample(standardSampler, vert.textureCoordinates).r;
|
|
||||||
return float4((sample >> 4)&3, (sample >> 2)&3, sample&3, 3.0) / 3.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment float4 sampleRed4Green4Blue4(SourceInterpolator vert [[stage_in]], texture2d<ushort> texture [[texture(0)]]) {
|
|
||||||
const auto sample = texture.sample(standardSampler, vert.textureCoordinates).rg;
|
|
||||||
return float4(sample.r&15, (sample.g >> 4)&15, sample.g&15, 15.0) / 15.0;
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user