mirror of
https://github.com/TomHarte/CLK.git
synced 2025-12-22 04:18:01 +00:00
Compare commits
2 Commits
master
...
MetalTempl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fcc04409d | ||
|
|
aa700caca2 |
@@ -47,7 +47,6 @@ struct Machine {
|
|||||||
DeclareField(automatic_tape_motor_control);
|
DeclareField(automatic_tape_motor_control);
|
||||||
declare_display_option();
|
declare_display_option();
|
||||||
declare_quickload_option();
|
declare_quickload_option();
|
||||||
limit_enum(&output, Configurable::Display::RGB, Configurable::Display::CompositeColour, -1);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -253,7 +253,6 @@ std::map<std::string, std::unique_ptr<Reflection::Struct>> Machine::AllOptionsBy
|
|||||||
Emplace(AppleII, Apple::II::Machine);
|
Emplace(AppleII, Apple::II::Machine);
|
||||||
Emplace(Archimedes, Archimedes::Machine);
|
Emplace(Archimedes, Archimedes::Machine);
|
||||||
Emplace(AtariST, Atari::ST::Machine);
|
Emplace(AtariST, Atari::ST::Machine);
|
||||||
Emplace(BBCMicro, BBCMicro::Machine);
|
|
||||||
Emplace(ColecoVision, Coleco::Vision::Machine);
|
Emplace(ColecoVision, Coleco::Vision::Machine);
|
||||||
Emplace(Electron, Electron::Machine);
|
Emplace(Electron, Electron::Machine);
|
||||||
Emplace(Enterprise, Enterprise::Machine);
|
Emplace(Enterprise, Enterprise::Machine);
|
||||||
|
|||||||
@@ -5714,7 +5714,7 @@
|
|||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = YES;
|
BuildIndependentTargetsInParallel = YES;
|
||||||
LastSwiftUpdateCheck = 0700;
|
LastSwiftUpdateCheck = 0700;
|
||||||
LastUpgradeCheck = 2620;
|
LastUpgradeCheck = 2610;
|
||||||
ORGANIZATIONNAME = "Thomas Harte";
|
ORGANIZATIONNAME = "Thomas Harte";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
4B055A691FAE763F0060FFFF = {
|
4B055A691FAE763F0060FFFF = {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "2620"
|
LastUpgradeVersion = "2610"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
</CommandLineArgument>
|
</CommandLineArgument>
|
||||||
<CommandLineArgument
|
<CommandLineArgument
|
||||||
argument = "--help"
|
argument = "--help"
|
||||||
isEnabled = "YES">
|
isEnabled = "NO">
|
||||||
</CommandLineArgument>
|
</CommandLineArgument>
|
||||||
</CommandLineArguments>
|
</CommandLineArguments>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "2620"
|
LastUpgradeVersion = "2610"
|
||||||
version = "1.8">
|
version = "1.8">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "2620"
|
LastUpgradeVersion = "2610"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|||||||
@@ -48,13 +48,20 @@ struct Uniforms {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr sampler standardSampler( coord::pixel,
|
// Although arbitrary, address::clamp_to_edge is used for compatibility all the way down
|
||||||
address::clamp_to_edge, // Although arbitrary, stick with this address mode for compatibility all the way to MTLFeatureSet_iOS_GPUFamily1_v1.
|
// to MTLFeatureSet_iOS_GPUFamily1_v1.
|
||||||
filter::nearest);
|
|
||||||
|
|
||||||
constexpr sampler linearSampler( coord::pixel,
|
constexpr sampler standardSampler(
|
||||||
address::clamp_to_edge, // Although arbitrary, stick with this address mode for compatibility all the way to MTLFeatureSet_iOS_GPUFamily1_v1.
|
coord::pixel,
|
||||||
filter::linear);
|
address::clamp_to_edge,
|
||||||
|
filter::nearest
|
||||||
|
);
|
||||||
|
|
||||||
|
constexpr sampler linearSampler(
|
||||||
|
coord::pixel,
|
||||||
|
address::clamp_to_edge,
|
||||||
|
filter::linear
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,8 +102,8 @@ struct Line {
|
|||||||
struct SourceInterpolator {
|
struct SourceInterpolator {
|
||||||
float4 position [[position]];
|
float4 position [[position]];
|
||||||
float2 textureCoordinates;
|
float2 textureCoordinates;
|
||||||
float unitColourPhase; // i.e. one unit per circle.
|
float unitColourPhase; // One unit per circle.
|
||||||
float colourPhase; // i.e. 2*pi units per circle, just regular radians.
|
float colourPhase; // Radians.
|
||||||
half colourAmplitude [[flat]];
|
half colourAmplitude [[flat]];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -107,13 +114,24 @@ struct CopyInterpolator {
|
|||||||
|
|
||||||
// MARK: - Vertex shaders.
|
// MARK: - Vertex shaders.
|
||||||
|
|
||||||
float2 textureLocation(constant Line *line, float offset, constant Uniforms &uniforms) {
|
float2 textureLocation(
|
||||||
|
constant Line *line,
|
||||||
|
const float offset,
|
||||||
|
constant Uniforms &uniforms
|
||||||
|
) {
|
||||||
|
const auto cyclesSinceRetrace =
|
||||||
|
mix(line->endPoints[0].cyclesSinceRetrace, line->endPoints[1].cyclesSinceRetrace, offset);
|
||||||
return float2(
|
return float2(
|
||||||
uniforms.cycleMultiplier * mix(line->endPoints[0].cyclesSinceRetrace, line->endPoints[1].cyclesSinceRetrace, offset),
|
uniforms.cycleMultiplier * cyclesSinceRetrace,
|
||||||
line->line + 0.5f);
|
line->line + 0.5f
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
float2 textureLocation(constant Scan *scan, float offset, constant Uniforms &) {
|
float2 textureLocation(
|
||||||
|
constant Scan *const scan,
|
||||||
|
const float offset,
|
||||||
|
constant Uniforms &
|
||||||
|
) {
|
||||||
return float2(
|
return float2(
|
||||||
mix(scan->endPoints[0].dataOffset, scan->endPoints[1].dataOffset, offset),
|
mix(scan->endPoints[0].dataOffset, scan->endPoints[1].dataOffset, offset),
|
||||||
scan->dataY + 0.5f);
|
scan->dataY + 0.5f);
|
||||||
@@ -121,9 +139,10 @@ float2 textureLocation(constant Scan *scan, float offset, constant Uniforms &) {
|
|||||||
|
|
||||||
template <typename Input> SourceInterpolator toDisplay(
|
template <typename Input> SourceInterpolator toDisplay(
|
||||||
constant Uniforms &uniforms [[buffer(1)]],
|
constant Uniforms &uniforms [[buffer(1)]],
|
||||||
constant Input *inputs [[buffer(0)]],
|
constant Input *const inputs [[buffer(0)]],
|
||||||
uint instanceID [[instance_id]],
|
const uint instanceID [[instance_id]],
|
||||||
uint vertexID [[vertex_id]]) {
|
const uint vertexID [[vertex_id]]
|
||||||
|
) {
|
||||||
SourceInterpolator output;
|
SourceInterpolator output;
|
||||||
|
|
||||||
// Get start and end vertices in regular float2 form.
|
// Get start and end vertices in regular float2 form.
|
||||||
@@ -152,7 +171,10 @@ template <typename Input> SourceInterpolator toDisplay(
|
|||||||
// Hence determine this quad's real shape, using vertexID to pick a corner.
|
// Hence determine this quad's real shape, using vertexID to pick a corner.
|
||||||
|
|
||||||
// position2d is now in the range [0, 1].
|
// position2d is now in the range [0, 1].
|
||||||
const float2 sourcePosition = start + (float(vertexID&2) * 0.5f) * tangent + (float(vertexID&1) - 0.5f) * normal * uniforms.lineWidth;
|
const float2 sourcePosition =
|
||||||
|
start +
|
||||||
|
(float(vertexID&2) * 0.5f) * tangent +
|
||||||
|
(float(vertexID&1) - 0.5f) * normal * uniforms.lineWidth;
|
||||||
const float2 position2d = (uniforms.sourceToDisplay * float3(sourcePosition, 1.0f)).xy;
|
const float2 position2d = (uniforms.sourceToDisplay * float3(sourcePosition, 1.0f)).xy;
|
||||||
|
|
||||||
output.position = float4(
|
output.position = float4(
|
||||||
@@ -165,38 +187,53 @@ template <typename Input> SourceInterpolator toDisplay(
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
// These next two assume the incoming geometry to be a four-vertex triangle strip; each instance will therefore
|
// These next two assume the incoming geometry to be a four-vertex triangle strip;
|
||||||
// produce a quad.
|
// each instance will therefore produce a quad.
|
||||||
|
|
||||||
vertex SourceInterpolator scanToDisplay( constant Uniforms &uniforms [[buffer(1)]],
|
vertex SourceInterpolator scanToDisplay(
|
||||||
constant Scan *scans [[buffer(0)]],
|
constant Uniforms &uniforms [[buffer(1)]],
|
||||||
uint instanceID [[instance_id]],
|
constant Scan *const scans [[buffer(0)]],
|
||||||
uint vertexID [[vertex_id]]) {
|
const uint instanceID [[instance_id]],
|
||||||
|
const uint vertexID [[vertex_id]]
|
||||||
|
) {
|
||||||
return toDisplay(uniforms, scans, instanceID, vertexID);
|
return toDisplay(uniforms, scans, instanceID, vertexID);
|
||||||
}
|
}
|
||||||
|
|
||||||
vertex SourceInterpolator lineToDisplay( constant Uniforms &uniforms [[buffer(1)]],
|
vertex SourceInterpolator lineToDisplay(
|
||||||
constant Line *lines [[buffer(0)]],
|
constant Uniforms &uniforms [[buffer(1)]],
|
||||||
uint instanceID [[instance_id]],
|
constant Line *const lines [[buffer(0)]],
|
||||||
uint vertexID [[vertex_id]]) {
|
const uint instanceID [[instance_id]],
|
||||||
|
const uint vertexID [[vertex_id]]
|
||||||
|
) {
|
||||||
return toDisplay(uniforms, lines, instanceID, vertexID);
|
return toDisplay(uniforms, lines, instanceID, vertexID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This assumes that it needs to generate endpoints for a line segment.
|
// Generates endpoints for a line segment.
|
||||||
|
vertex SourceInterpolator scanToComposition(
|
||||||
vertex SourceInterpolator scanToComposition( constant Uniforms &uniforms [[buffer(1)]],
|
constant Uniforms &uniforms [[buffer(1)]],
|
||||||
constant Scan *scans [[buffer(0)]],
|
constant Scan *const scans [[buffer(0)]],
|
||||||
uint instanceID [[instance_id]],
|
const uint instanceID [[instance_id]],
|
||||||
uint vertexID [[vertex_id]],
|
const uint vertexID [[vertex_id]],
|
||||||
texture2d<float> texture [[texture(0)]]) {
|
const texture2d<float> texture [[texture(0)]]
|
||||||
|
) {
|
||||||
SourceInterpolator result;
|
SourceInterpolator result;
|
||||||
|
|
||||||
// Populate result as if direct texture access were available.
|
// Populate result as if direct texture access were available.
|
||||||
result.position.x = uniforms.cycleMultiplier * mix(scans[instanceID].endPoints[0].cyclesSinceRetrace, scans[instanceID].endPoints[1].cyclesSinceRetrace, float(vertexID));
|
result.position.x =
|
||||||
|
uniforms.cycleMultiplier *
|
||||||
|
mix(
|
||||||
|
scans[instanceID].endPoints[0].cyclesSinceRetrace,
|
||||||
|
scans[instanceID].endPoints[1].cyclesSinceRetrace,
|
||||||
|
float(vertexID)
|
||||||
|
);
|
||||||
result.position.y = scans[instanceID].line;
|
result.position.y = scans[instanceID].line;
|
||||||
result.position.zw = float2(0.0f, 1.0f);
|
result.position.zw = float2(0.0f, 1.0f);
|
||||||
|
|
||||||
result.textureCoordinates.x = mix(scans[instanceID].endPoints[0].dataOffset, scans[instanceID].endPoints[1].dataOffset, float(vertexID));
|
result.textureCoordinates.x = mix(
|
||||||
|
scans[instanceID].endPoints[0].dataOffset,
|
||||||
|
scans[instanceID].endPoints[1].dataOffset,
|
||||||
|
float(vertexID)
|
||||||
|
);
|
||||||
result.textureCoordinates.y = scans[instanceID].dataY;
|
result.textureCoordinates.y = scans[instanceID].dataY;
|
||||||
|
|
||||||
result.unitColourPhase = mix(
|
result.unitColourPhase = mix(
|
||||||
@@ -216,72 +253,121 @@ vertex SourceInterpolator scanToComposition( constant Uniforms &uniforms [[buffe
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
vertex CopyInterpolator copyVertex(uint vertexID [[vertex_id]], texture2d<float> texture [[texture(0)]]) {
|
vertex CopyInterpolator copyVertex(
|
||||||
CopyInterpolator vert;
|
const uint vertexID [[vertex_id]],
|
||||||
|
const texture2d<float> texture [[texture(0)]]
|
||||||
|
) {
|
||||||
const uint x = vertexID & 1;
|
const uint x = vertexID & 1;
|
||||||
const uint y = (vertexID >> 1) & 1;
|
const uint y = (vertexID >> 1) & 1;
|
||||||
|
|
||||||
vert.textureCoordinates = float2(
|
return CopyInterpolator{
|
||||||
x * texture.get_width(),
|
.textureCoordinates = float2(
|
||||||
y * texture.get_height()
|
x * texture.get_width(),
|
||||||
);
|
y * texture.get_height()
|
||||||
vert.position = float4(
|
),
|
||||||
float(x) * 2.0 - 1.0,
|
.position = float4(
|
||||||
1.0 - float(y) * 2.0,
|
float(x) * 2.0 - 1.0,
|
||||||
0.0,
|
1.0 - float(y) * 2.0,
|
||||||
1.0
|
0.0,
|
||||||
);
|
1.0
|
||||||
|
)
|
||||||
return vert;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Various input format conversion samplers.
|
// MARK: - Input format conversion samplers.
|
||||||
|
|
||||||
half2 quadrature(float phase) {
|
enum class InputEncoding {
|
||||||
|
Luminance1,
|
||||||
|
Luminance8,
|
||||||
|
PhaseLinkedLuminance8,
|
||||||
|
|
||||||
|
Luminance8Phase8,
|
||||||
|
|
||||||
|
Red8Green8Blue8,
|
||||||
|
Red4Green4Blue4,
|
||||||
|
Red2Green2Blue2,
|
||||||
|
Red1Green1Blue1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define the per-pixel type of input textures based on data format.
|
||||||
|
template <InputEncoding> struct DataFormat { using type = half; };
|
||||||
|
template<> struct DataFormat<InputEncoding::Luminance1> { using type = ushort; };
|
||||||
|
template<> struct DataFormat<InputEncoding::Red4Green4Blue4> { using type = ushort; };
|
||||||
|
template<> struct DataFormat<InputEncoding::Red2Green2Blue2> { using type = ushort; };
|
||||||
|
template<> struct DataFormat<InputEncoding::Red1Green1Blue1> { using type = ushort; };
|
||||||
|
template <InputEncoding encoding> using data_t = typename DataFormat<encoding>::type;
|
||||||
|
|
||||||
|
// Internal type aliases, correlating to the input data and intermediate buffers.
|
||||||
|
using Composite = half; // i.e. a single sample of composite video.
|
||||||
|
using LuminanceChrominance = half2; // i.e. a single sample of s-video; .x = luminance; .y = chroma.
|
||||||
|
|
||||||
|
using UnfilteredYUVAmplitude = half4; // .x = pointwise luminance (colour subcarrier not yet removed);
|
||||||
|
// .yz = two chrominance channels (with noise at twice the subcarrier frequency);
|
||||||
|
// .w = amplitude of the chrominance channels.
|
||||||
|
|
||||||
|
using RGB = half3;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
half2 quadrature(const float phase) {
|
||||||
return half2(cos(phase), sin(phase));
|
return half2(cos(phase), sin(phase));
|
||||||
}
|
}
|
||||||
|
|
||||||
half4 composite(half level, half2 quadrature, half amplitude) {
|
UnfilteredYUVAmplitude composite(const half level, const half2 quadrature, const half amplitude) {
|
||||||
return half4(
|
return half4(
|
||||||
level,
|
level,
|
||||||
half2(0.5f) + quadrature*half(0.5f),
|
half2(0.5f) + quadrature*half(0.5f),
|
||||||
amplitude
|
amplitude
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The luminance formats can be sampled either in their natural format, or to the intermediate
|
// The luminance formats can be sampled either in their natural format, or to the intermediate
|
||||||
// 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.
|
||||||
|
|
||||||
half convertLuminance1(SourceInterpolator vert [[stage_in]], texture2d<ushort> texture [[texture(0)]]) {
|
// MARK: - Composite sampling.
|
||||||
|
|
||||||
|
template <InputEncoding encoding> half sample_composite(SourceInterpolator, texture2d<data_t<encoding>>);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
Composite sample_composite<InputEncoding::Luminance1>(
|
||||||
|
SourceInterpolator vert [[stage_in]],
|
||||||
|
texture2d<ushort> texture [[texture(0)]]
|
||||||
|
) {
|
||||||
return clamp(half(texture.sample(standardSampler, vert.textureCoordinates).r), half(0.0f), half(1.0f));
|
return clamp(half(texture.sample(standardSampler, vert.textureCoordinates).r), half(0.0f), half(1.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
half convertLuminance8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
|
template <>
|
||||||
|
Composite sample_composite<InputEncoding::Luminance8>(
|
||||||
|
SourceInterpolator vert [[stage_in]],
|
||||||
|
texture2d<half> texture [[texture(0)]]
|
||||||
|
) {
|
||||||
return texture.sample(standardSampler, vert.textureCoordinates).r;
|
return texture.sample(standardSampler, vert.textureCoordinates).r;
|
||||||
}
|
}
|
||||||
|
|
||||||
half convertPhaseLinkedLuminance8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
|
template <>
|
||||||
|
Composite sample_composite<InputEncoding::PhaseLinkedLuminance8>(
|
||||||
|
SourceInterpolator vert [[stage_in]],
|
||||||
|
texture2d<half> texture [[texture(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);
|
const auto sample = texture.sample(standardSampler, vert.textureCoordinates);
|
||||||
return sample[offset];
|
return sample[offset];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#define CompositeSet(name, type) \
|
#define CompositeSet(name, type) \
|
||||||
fragment half4 sample##name(SourceInterpolator vert [[stage_in]], texture2d<type> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
|
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; \
|
const half luminance = sample_composite<InputEncoding::name>(vert, texture) * uniforms.outputMultiplier; \
|
||||||
return half4(half3(luminance), uniforms.outputAlpha); \
|
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)]]) { \
|
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); \
|
const half luminance = pow(sample_composite<InputEncoding::name>(vert, texture) * uniforms.outputMultiplier, uniforms.outputGamma); \
|
||||||
return half4(half3(luminance), uniforms.outputAlpha); \
|
return half4(half3(luminance), uniforms.outputAlpha); \
|
||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
fragment half4 compositeSample##name(SourceInterpolator vert [[stage_in]], texture2d<type> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
|
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; \
|
const half luminance = sample_composite<InputEncoding::name>(vert, texture) * uniforms.outputMultiplier; \
|
||||||
return composite(luminance, quadrature(vert.colourPhase), vert.colourAmplitude); \
|
return composite(luminance, quadrature(vert.colourPhase), vert.colourAmplitude); \
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,22 +379,35 @@ CompositeSet(PhaseLinkedLuminance8, half);
|
|||||||
|
|
||||||
// The luminance/phase format can produce either composite or S-Video.
|
// The luminance/phase format can produce either composite or S-Video.
|
||||||
|
|
||||||
/// @returns A 2d vector comprised where .x = luminance; .y = chroma.
|
// MARK: - SVideo sampling.
|
||||||
half2 convertLuminance8Phase8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
|
|
||||||
const auto luminancePhase = texture.sample(standardSampler, vert.textureCoordinates).rg;
|
template <InputEncoding encoding>
|
||||||
const half phaseOffset = 3.141592654 * 4.0 * luminancePhase.g;
|
LuminanceChrominance sample_svideo(
|
||||||
const half rawChroma = step(luminancePhase.g, half(0.75f)) * cos(vert.colourPhase + phaseOffset);
|
SourceInterpolator vert [[stage_in]],
|
||||||
return half2(luminancePhase.r, rawChroma);
|
texture2d<half> texture [[texture(0)]]
|
||||||
|
) {
|
||||||
|
if(encoding == InputEncoding::Luminance8Phase8) {
|
||||||
|
const auto luminancePhase = texture.sample(standardSampler, vert.textureCoordinates).rg;
|
||||||
|
const half phaseOffset = 3.141592654 * 4.0 * luminancePhase.g;
|
||||||
|
const half rawChroma = step(luminancePhase.g, half(0.75f)) * cos(vert.colourPhase + phaseOffset);
|
||||||
|
return half2(luminancePhase.r, rawChroma);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: sample_rgb and convert.
|
||||||
|
return half2(0.0, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment half4 compositeSampleLuminance8Phase8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
|
fragment UnfilteredYUVAmplitude compositeSampleLuminance8Phase8(
|
||||||
const half2 luminanceChroma = convertLuminance8Phase8(vert, texture);
|
SourceInterpolator vert [[stage_in]],
|
||||||
|
texture2d<half> texture [[texture(0)]]
|
||||||
|
) {
|
||||||
|
const half2 luminanceChroma = sample_svideo<InputEncoding::Luminance8Phase8>(vert, texture);
|
||||||
const half luminance = mix(luminanceChroma.r, luminanceChroma.g, vert.colourAmplitude);
|
const half luminance = mix(luminanceChroma.r, luminanceChroma.g, vert.colourAmplitude);
|
||||||
return composite(luminance, quadrature(vert.colourPhase), 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 = sample_svideo<InputEncoding::Luminance8Phase8>(vert, texture);
|
||||||
const half2 qam = quadrature(vert.colourPhase) * half(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,
|
||||||
@@ -316,50 +415,52 @@ fragment half4 sampleLuminance8Phase8(SourceInterpolator vert [[stage_in]], text
|
|||||||
}
|
}
|
||||||
|
|
||||||
fragment half4 directCompositeSampleLuminance8Phase8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(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 = sample_svideo<InputEncoding::Luminance8Phase8>(vert, texture);
|
||||||
const half luminance = mix(luminanceChroma.r * uniforms.outputMultiplier, luminanceChroma.g, vert.colourAmplitude);
|
const half luminance = mix(luminanceChroma.r * uniforms.outputMultiplier, luminanceChroma.g, vert.colourAmplitude);
|
||||||
return half4(half3(luminance), uniforms.outputAlpha);
|
return half4(half3(luminance), uniforms.outputAlpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment half4 directCompositeSampleLuminance8Phase8WithGamma(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) {
|
fragment half4 directCompositeSampleLuminance8Phase8WithGamma(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) {
|
||||||
const half2 luminanceChroma = convertLuminance8Phase8(vert, texture);
|
const half2 luminanceChroma = sample_svideo<InputEncoding::Luminance8Phase8>(vert, texture);
|
||||||
const half luminance = mix(pow(luminanceChroma.r * uniforms.outputMultiplier, uniforms.outputGamma), luminanceChroma.g, vert.colourAmplitude);
|
const half luminance = mix(pow(luminanceChroma.r * uniforms.outputMultiplier, uniforms.outputGamma), luminanceChroma.g, vert.colourAmplitude);
|
||||||
return half4(half3(luminance), uniforms.outputAlpha);
|
return half4(half3(luminance), uniforms.outputAlpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// All the RGB formats can produce RGB, composite or S-Video.
|
// MARK: - RGB sampling.
|
||||||
|
|
||||||
half3 convertRed8Green8Blue8(SourceInterpolator vert, texture2d<half> texture) {
|
template <InputEncoding encoding> RGB sample_rgb(SourceInterpolator, texture2d<data_t<encoding>>);
|
||||||
|
|
||||||
|
template<> RGB sample_rgb<InputEncoding::Red8Green8Blue8>(const SourceInterpolator vert, const texture2d<half> texture) {
|
||||||
return texture.sample(standardSampler, vert.textureCoordinates).rgb;
|
return texture.sample(standardSampler, vert.textureCoordinates).rgb;
|
||||||
}
|
}
|
||||||
|
|
||||||
half3 convertRed4Green4Blue4(SourceInterpolator vert, texture2d<ushort> texture) {
|
template<> RGB sample_rgb<InputEncoding::Red4Green4Blue4>(const SourceInterpolator vert, const 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) / 15.0f;
|
return half3(sample.r&15, (sample.g >> 4)&15, sample.g&15) / 15.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
half3 convertRed2Green2Blue2(SourceInterpolator vert, texture2d<ushort> texture) {
|
template<> RGB sample_rgb<InputEncoding::Red2Green2Blue2>(const SourceInterpolator vert, const 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) / 3.0f;
|
return half3((sample >> 4)&3, (sample >> 2)&3, sample&3) / 3.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
half3 convertRed1Green1Blue1(SourceInterpolator vert, texture2d<ushort> texture) {
|
template<> RGB sample_rgb<InputEncoding::Red1Green1Blue1>(const SourceInterpolator vert, const texture2d<ushort> texture) {
|
||||||
const auto sample = texture.sample(standardSampler, vert.textureCoordinates).r;
|
const auto sample = texture.sample(standardSampler, vert.textureCoordinates).r;
|
||||||
return clamp(half3(sample&4, sample&2, sample&1), half(0.0f), half(1.0f));
|
return clamp(half3(sample&4, sample&2, sample&1), half(0.0f), half(1.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
#define DeclareShaders(name, pixelType) \
|
#define DeclareShaders(name, pixelType) \
|
||||||
fragment half4 sample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(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) * uniforms.outputMultiplier, uniforms.outputAlpha); \
|
return half4(sample_rgb<InputEncoding::name>(vert, texture) * uniforms.outputMultiplier, uniforms.outputAlpha); \
|
||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
fragment half4 sample##name##WithGamma(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
|
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.outputMultiplier, uniforms.outputGamma), uniforms.outputAlpha); \
|
return half4(pow(sample_rgb<InputEncoding::name>(vert, texture) * uniforms.outputMultiplier, 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 * convert##name(vert, texture); \
|
const auto colour = uniforms.fromRGB * sample_rgb<InputEncoding::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( \
|
||||||
@@ -370,7 +471,7 @@ half3 convertRed1Green1Blue1(SourceInterpolator vert, texture2d<ushort> texture)
|
|||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
half composite##name(SourceInterpolator vert, texture2d<pixelType> texture, constant Uniforms &uniforms, half2 colourSubcarrier) { \
|
half composite##name(SourceInterpolator vert, texture2d<pixelType> texture, constant Uniforms &uniforms, half2 colourSubcarrier) { \
|
||||||
const auto colour = uniforms.fromRGB * convert##name(vert, texture); \
|
const auto colour = uniforms.fromRGB * sample_rgb<InputEncoding::name>(vert, texture); \
|
||||||
return mix(colour.r, dot(colour.gb, colourSubcarrier), half(vert.colourAmplitude)); \
|
return mix(colour.r, dot(colour.gb, colourSubcarrier), half(vert.colourAmplitude)); \
|
||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
@@ -394,27 +495,37 @@ DeclareShaders(Red4Green4Blue4, ushort)
|
|||||||
DeclareShaders(Red2Green2Blue2, ushort)
|
DeclareShaders(Red2Green2Blue2, ushort)
|
||||||
DeclareShaders(Red1Green1Blue1, ushort)
|
DeclareShaders(Red1Green1Blue1, ushort)
|
||||||
|
|
||||||
|
// MARK: - Copying and solid fills.
|
||||||
|
|
||||||
|
/// Point samples @c texture.
|
||||||
fragment half4 copyFragment(CopyInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
|
fragment half4 copyFragment(CopyInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
|
||||||
return texture.sample(standardSampler, vert.textureCoordinates);
|
return texture.sample(standardSampler, vert.textureCoordinates);
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment half4 interpolateFragment(CopyInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
|
/// Bilinearly samples @c texture.
|
||||||
|
fragment half4 interpolateFragment(
|
||||||
|
CopyInterpolator vert [[stage_in]],
|
||||||
|
texture2d<half> texture [[texture(0)]]
|
||||||
|
) {
|
||||||
return texture.sample(linearSampler, vert.textureCoordinates);
|
return texture.sample(linearSampler, vert.textureCoordinates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fills with black.
|
||||||
fragment half4 clearFragment(constant Uniforms &uniforms [[buffer(0)]]) {
|
fragment half4 clearFragment(constant Uniforms &uniforms [[buffer(0)]]) {
|
||||||
return half4(0.0, 0.0, 0.0, uniforms.outputAlpha);
|
return half4(0.0, 0.0, 0.0, uniforms.outputAlpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Compute kernels
|
// MARK: - Compute kernels.
|
||||||
|
|
||||||
/// Given input pixels of the form (luminance, 0.5 + 0.5*chrominance*cos(phase), 0.5 + 0.5*chrominance*sin(phase)), applies a lowpass
|
/// Given input pixels of the form (luminance, 0.5 + 0.5*chrominance*cos(phase), 0.5 + 0.5*chrominance*sin(phase)), applies a lowpass
|
||||||
/// filter to the two chrominance parts, then uses the toRGB matrix to convert to RGB and stores.
|
/// filter to the two chrominance parts, then uses the toRGB matrix to convert to RGB and stores.
|
||||||
template <bool applyGamma> void filterChromaKernel( texture2d<half, access::read> inTexture [[texture(0)]],
|
template <bool applyGamma> void filterChromaKernel(
|
||||||
texture2d<half, access::write> outTexture [[texture(1)]],
|
const texture2d<half, access::read> inTexture [[texture(0)]],
|
||||||
uint2 gid [[thread_position_in_grid]],
|
const texture2d<half, access::write> outTexture [[texture(1)]],
|
||||||
constant Uniforms &uniforms [[buffer(0)]],
|
const uint2 gid [[thread_position_in_grid]],
|
||||||
constant int &offset [[buffer(1)]]) {
|
constant Uniforms &uniforms [[buffer(0)]],
|
||||||
|
constant int &offset [[buffer(1)]]
|
||||||
|
) {
|
||||||
constexpr half4 moveToZero(0.0f, 0.5f, 0.5f, 0.0f);
|
constexpr half4 moveToZero(0.0f, 0.5f, 0.5f, 0.0f);
|
||||||
const half4 rawSamples[] = {
|
const half4 rawSamples[] = {
|
||||||
inTexture.read(gid + uint2(0, offset)) - moveToZero,
|
inTexture.read(gid + uint2(0, offset)) - moveToZero,
|
||||||
@@ -449,23 +560,33 @@ template <bool applyGamma> void filterChromaKernel( texture2d<half, access::read
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kernel void filterChromaKernelNoGamma( texture2d<half, access::read> inTexture [[texture(0)]],
|
kernel void filterChromaKernelNoGamma(
|
||||||
texture2d<half, access::write> outTexture [[texture(1)]],
|
const texture2d<half, access::read> inTexture [[texture(0)]],
|
||||||
uint2 gid [[thread_position_in_grid]],
|
const texture2d<half, access::write> outTexture [[texture(1)]],
|
||||||
constant Uniforms &uniforms [[buffer(0)]],
|
const uint2 gid [[thread_position_in_grid]],
|
||||||
constant int &offset [[buffer(1)]]) {
|
constant Uniforms &uniforms [[buffer(0)]],
|
||||||
|
constant int &offset [[buffer(1)]]
|
||||||
|
) {
|
||||||
filterChromaKernel<false>(inTexture, outTexture, gid, uniforms, offset);
|
filterChromaKernel<false>(inTexture, outTexture, gid, uniforms, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
kernel void filterChromaKernelWithGamma( texture2d<half, access::read> inTexture [[texture(0)]],
|
kernel void filterChromaKernelWithGamma(
|
||||||
texture2d<half, access::write> outTexture [[texture(1)]],
|
const texture2d<half, access::read> inTexture [[texture(0)]],
|
||||||
uint2 gid [[thread_position_in_grid]],
|
const texture2d<half, access::write> outTexture [[texture(1)]],
|
||||||
constant Uniforms &uniforms [[buffer(0)]],
|
const uint2 gid [[thread_position_in_grid]],
|
||||||
constant int &offset [[buffer(1)]]) {
|
constant Uniforms &uniforms [[buffer(0)]],
|
||||||
|
constant int &offset [[buffer(1)]]
|
||||||
|
) {
|
||||||
filterChromaKernel<true>(inTexture, outTexture, gid, uniforms, offset);
|
filterChromaKernel<true>(inTexture, outTexture, gid, uniforms, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSeparatedLumaChroma(half luminance, half4 centreSample, texture2d<half, access::write> outTexture, uint2 gid, int offset) {
|
void setSeparatedLumaChroma(
|
||||||
|
const half luminance,
|
||||||
|
const half4 centreSample,
|
||||||
|
const texture2d<half, access::write> outTexture,
|
||||||
|
const uint2 gid,
|
||||||
|
const int offset
|
||||||
|
) {
|
||||||
// The mix/steps below ensures that the absence of a colour burst leads the colour subcarrier to be discarded.
|
// The mix/steps below ensures that the absence of a colour burst leads the colour subcarrier to be discarded.
|
||||||
const half isColour = step(half(0.01f), centreSample.a);
|
const half isColour = step(half(0.01f), centreSample.a);
|
||||||
const half chroma = (centreSample.r - luminance) / mix(half(1.0f), centreSample.a, isColour);
|
const half chroma = (centreSample.r - luminance) / mix(half(1.0f), centreSample.a, isColour);
|
||||||
@@ -488,11 +609,13 @@ void setSeparatedLumaChroma(half luminance, half4 centreSample, texture2d<half,
|
|||||||
/// (luminance, 0.5 + 0.5*chrominance*cos(phase), 0.5 + 0.5*chrominance*sin(phase))
|
/// (luminance, 0.5 + 0.5*chrominance*cos(phase), 0.5 + 0.5*chrominance*sin(phase))
|
||||||
///
|
///
|
||||||
/// i.e. the input form for the filterChromaKernel, above].
|
/// i.e. the input form for the filterChromaKernel, above].
|
||||||
kernel void separateLumaKernel15( texture2d<half, access::read> inTexture [[texture(0)]],
|
kernel void separateLumaKernel15(
|
||||||
texture2d<half, access::write> outTexture [[texture(1)]],
|
const texture2d<half, access::read> inTexture [[texture(0)]],
|
||||||
uint2 gid [[thread_position_in_grid]],
|
const texture2d<half, access::write> outTexture [[texture(1)]],
|
||||||
constant Uniforms &uniforms [[buffer(0)]],
|
const uint2 gid [[thread_position_in_grid]],
|
||||||
constant int &offset [[buffer(1)]]) {
|
constant Uniforms &uniforms [[buffer(0)]],
|
||||||
|
constant int &offset [[buffer(1)]]
|
||||||
|
) {
|
||||||
const half4 centreSample = inTexture.read(gid + uint2(7, offset));
|
const half4 centreSample = inTexture.read(gid + uint2(7, offset));
|
||||||
const half rawSamples[] = {
|
const half rawSamples[] = {
|
||||||
inTexture.read(gid + uint2(0, offset)).r, inTexture.read(gid + uint2(1, offset)).r,
|
inTexture.read(gid + uint2(0, offset)).r, inTexture.read(gid + uint2(1, offset)).r,
|
||||||
@@ -516,11 +639,13 @@ kernel void separateLumaKernel15( texture2d<half, access::read> inTexture [[text
|
|||||||
return setSeparatedLumaChroma(luminance, centreSample, outTexture, gid, offset);
|
return setSeparatedLumaChroma(luminance, centreSample, outTexture, gid, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
kernel void separateLumaKernel9( texture2d<half, access::read> inTexture [[texture(0)]],
|
kernel void separateLumaKernel9(
|
||||||
texture2d<half, access::write> outTexture [[texture(1)]],
|
const texture2d<half, access::read> inTexture [[texture(0)]],
|
||||||
uint2 gid [[thread_position_in_grid]],
|
const texture2d<half, access::write> outTexture [[texture(1)]],
|
||||||
constant Uniforms &uniforms [[buffer(0)]],
|
const uint2 gid [[thread_position_in_grid]],
|
||||||
constant int &offset [[buffer(1)]]) {
|
constant Uniforms &uniforms [[buffer(0)]],
|
||||||
|
constant int &offset [[buffer(1)]]
|
||||||
|
) {
|
||||||
const half4 centreSample = inTexture.read(gid + uint2(7, offset));
|
const half4 centreSample = inTexture.read(gid + uint2(7, offset));
|
||||||
const half rawSamples[] = {
|
const half rawSamples[] = {
|
||||||
inTexture.read(gid + uint2(3, offset)).r, inTexture.read(gid + uint2(4, offset)).r,
|
inTexture.read(gid + uint2(3, offset)).r, inTexture.read(gid + uint2(4, offset)).r,
|
||||||
@@ -540,11 +665,13 @@ kernel void separateLumaKernel9( texture2d<half, access::read> inTexture [[textu
|
|||||||
return setSeparatedLumaChroma(luminance, centreSample, outTexture, gid, offset);
|
return setSeparatedLumaChroma(luminance, centreSample, outTexture, gid, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
kernel void separateLumaKernel7( texture2d<half, access::read> inTexture [[texture(0)]],
|
kernel void separateLumaKernel7(
|
||||||
texture2d<half, access::write> outTexture [[texture(1)]],
|
const texture2d<half, access::read> inTexture [[texture(0)]],
|
||||||
uint2 gid [[thread_position_in_grid]],
|
const texture2d<half, access::write> outTexture [[texture(1)]],
|
||||||
constant Uniforms &uniforms [[buffer(0)]],
|
const uint2 gid [[thread_position_in_grid]],
|
||||||
constant int &offset [[buffer(1)]]) {
|
constant Uniforms &uniforms [[buffer(0)]],
|
||||||
|
constant int &offset [[buffer(1)]]
|
||||||
|
) {
|
||||||
const half4 centreSample = inTexture.read(gid + uint2(7, offset));
|
const half4 centreSample = inTexture.read(gid + uint2(7, offset));
|
||||||
const half rawSamples[] = {
|
const half rawSamples[] = {
|
||||||
inTexture.read(gid + uint2(4, offset)).r,
|
inTexture.read(gid + uint2(4, offset)).r,
|
||||||
@@ -564,11 +691,13 @@ kernel void separateLumaKernel7( texture2d<half, access::read> inTexture [[textu
|
|||||||
return setSeparatedLumaChroma(luminance, centreSample, outTexture, gid, offset);
|
return setSeparatedLumaChroma(luminance, centreSample, outTexture, gid, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
kernel void separateLumaKernel5( texture2d<half, access::read> inTexture [[texture(0)]],
|
kernel void separateLumaKernel5(
|
||||||
texture2d<half, access::write> outTexture [[texture(1)]],
|
const texture2d<half, access::read> inTexture [[texture(0)]],
|
||||||
uint2 gid [[thread_position_in_grid]],
|
const texture2d<half, access::write> outTexture [[texture(1)]],
|
||||||
constant Uniforms &uniforms [[buffer(0)]],
|
const uint2 gid [[thread_position_in_grid]],
|
||||||
constant int &offset [[buffer(1)]]) {
|
constant Uniforms &uniforms [[buffer(0)]],
|
||||||
|
constant int &offset [[buffer(1)]]
|
||||||
|
) {
|
||||||
const half4 centreSample = inTexture.read(gid + uint2(7, offset));
|
const half4 centreSample = inTexture.read(gid + uint2(7, offset));
|
||||||
const half rawSamples[] = {
|
const half rawSamples[] = {
|
||||||
inTexture.read(gid + uint2(5, offset)).r, inTexture.read(gid + uint2(6, offset)).r,
|
inTexture.read(gid + uint2(5, offset)).r, inTexture.read(gid + uint2(6, offset)).r,
|
||||||
@@ -586,7 +715,9 @@ kernel void separateLumaKernel5( texture2d<half, access::read> inTexture [[textu
|
|||||||
return setSeparatedLumaChroma(luminance, centreSample, outTexture, gid, offset);
|
return setSeparatedLumaChroma(luminance, centreSample, outTexture, gid, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
kernel void clearKernel( texture2d<half, access::write> outTexture [[texture(0)]],
|
kernel void clearKernel(
|
||||||
uint2 gid [[thread_position_in_grid]]) {
|
const texture2d<half, access::write> outTexture [[texture(0)]],
|
||||||
|
const uint2 gid [[thread_position_in_grid]]
|
||||||
|
) {
|
||||||
outTexture.write(half4(0.0f, 0.0f, 0.0f, 1.0f), gid);
|
outTexture.write(half4(0.0f, 0.0f, 0.0f, 1.0f), gid);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
#include <QtWidgets>
|
#include <QtWidgets>
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "../../Numeric/CRC.hpp"
|
#include "../../Numeric/CRC.hpp"
|
||||||
#include "../../Configurable/StandardOptions.hpp"
|
#include "../../Configurable/StandardOptions.hpp"
|
||||||
@@ -402,74 +401,11 @@ void MainWindow::launchMachine() {
|
|||||||
|
|
||||||
// Add machine-specific UI.
|
// Add machine-specific UI.
|
||||||
const std::string settingsPrefix = Machine::ShortNameForTargetMachine(machineType);
|
const std::string settingsPrefix = Machine::ShortNameForTargetMachine(machineType);
|
||||||
auto configurableMachine = machine->configurable_device();
|
|
||||||
if(configurableMachine) {
|
|
||||||
auto options = configurableMachine->get_options();
|
|
||||||
const auto allKeys = options->all_keys();
|
|
||||||
const auto allDisplayValues = options->values_for(Configurable::Options::DisplayOptionName);
|
|
||||||
const auto hasDynamicCrop = std::find(allKeys.begin(), allKeys.end(), Configurable::Options::DynamicCropOptionName) != allKeys.end();
|
|
||||||
if(hasDynamicCrop || allDisplayValues.size() > 1) {
|
|
||||||
const auto contains = [&](const Configurable::Display option) {
|
|
||||||
const auto name = Reflection::Enum::to_string<Configurable::Display>(option);
|
|
||||||
return std::find(allDisplayValues.begin(), allDisplayValues.end(), name) != allDisplayValues.end();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const bool hasCompositeColour = contains(Configurable::Display::CompositeColour);
|
|
||||||
const bool hasCompositeMonochrome = contains(Configurable::Display::CompositeMonochrome);
|
|
||||||
const bool hasSVideo = contains(Configurable::Display::SVideo);
|
|
||||||
const bool hasRGB = contains(Configurable::Display::RGB);
|
|
||||||
|
|
||||||
const bool differentiateComposite = hasCompositeColour && hasCompositeMonochrome;
|
|
||||||
const bool hasMultipleTelevisionConnections = hasSVideo && (hasCompositeColour || hasCompositeMonochrome);
|
|
||||||
const bool hasNonCompositeConnections = hasSVideo || hasRGB;
|
|
||||||
|
|
||||||
const auto compositeColourName = [&]() {
|
|
||||||
if(!hasNonCompositeConnections) {
|
|
||||||
return "Colour";
|
|
||||||
}
|
|
||||||
if(hasMultipleTelevisionConnections) {
|
|
||||||
return differentiateComposite ? "Colour Composite" : "Composite";
|
|
||||||
} else {
|
|
||||||
return differentiateComposite ? "Colour Television" : "Television";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto compositeMonochromeName = [&]() {
|
|
||||||
if(!hasNonCompositeConnections) {
|
|
||||||
return "Monochrome";
|
|
||||||
}
|
|
||||||
if(hasMultipleTelevisionConnections) {
|
|
||||||
return differentiateComposite ? "Monochrome Composite" : "Composite";
|
|
||||||
} else {
|
|
||||||
return differentiateComposite ? "Black and White Television" : "Television";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto rgbName = [&]() {
|
|
||||||
return hasMultipleTelevisionConnections ? "RGB" : "Monitor";
|
|
||||||
};
|
|
||||||
|
|
||||||
addDisplayMenu(
|
|
||||||
settingsPrefix,
|
|
||||||
hasCompositeColour ? compositeColourName() : "",
|
|
||||||
hasCompositeMonochrome ? compositeMonochromeName() : "",
|
|
||||||
hasSVideo ? "S-Video" : "",
|
|
||||||
hasRGB ? rgbName() : "",
|
|
||||||
hasDynamicCrop
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The ZX80 and ZX81 have a specialised version of this.
|
|
||||||
// It might become general later if I generalite automatic tape motor control, which I probably should.
|
|
||||||
if(machineType != Analyser::Machine::ZX8081) {
|
|
||||||
const auto hasQuickLoad = std::find(allKeys.begin(), allKeys.end(), Configurable::Options::QuickLoadOptionName) != allKeys.end();
|
|
||||||
const auto hasQuickBoot = std::find(allKeys.begin(), allKeys.end(), Configurable::Options::QuickBootOptionName) != allKeys.end();
|
|
||||||
addEnhancementsMenu(settingsPrefix, hasQuickLoad, hasQuickBoot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(machineType) {
|
switch(machineType) {
|
||||||
|
case Analyser::Machine::AmstradCPC:
|
||||||
|
addDisplayMenu(settingsPrefix, "Television", "", "", "Monitor");
|
||||||
|
break;
|
||||||
|
|
||||||
case Analyser::Machine::AppleII:
|
case Analyser::Machine::AppleII:
|
||||||
addAppleIIMenu();
|
addAppleIIMenu();
|
||||||
break;
|
break;
|
||||||
@@ -478,10 +414,58 @@ void MainWindow::launchMachine() {
|
|||||||
addAtari2600Menu();
|
addAtari2600Menu();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Analyser::Machine::Archimedes:
|
||||||
|
addEnhancementsMenu(settingsPrefix, true, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Analyser::Machine::AtariST:
|
||||||
|
addDisplayMenu(settingsPrefix, "Television", "", "", "Monitor");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Analyser::Machine::ColecoVision:
|
||||||
|
addDisplayMenu(settingsPrefix, "Composite", "", "S-Video", "");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Analyser::Machine::Electron:
|
||||||
|
addDisplayMenu(settingsPrefix, "Composite", "", "S-Video", "RGB");
|
||||||
|
addEnhancementsMenu(settingsPrefix, true, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Analyser::Machine::Enterprise:
|
||||||
|
addDisplayMenu(settingsPrefix, "Composite", "", "", "RGB");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Analyser::Machine::Macintosh:
|
||||||
|
addEnhancementsMenu(settingsPrefix, false, true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Analyser::Machine::MasterSystem:
|
||||||
|
addDisplayMenu(settingsPrefix, "Composite", "", "S-Video", "SCART");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Analyser::Machine::MSX:
|
||||||
|
addDisplayMenu(settingsPrefix, "Composite", "", "S-Video", "SCART");
|
||||||
|
addEnhancementsMenu(settingsPrefix, true, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Analyser::Machine::Oric:
|
||||||
|
addDisplayMenu(settingsPrefix, "Composite", "", "", "SCART");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Analyser::Machine::Vic20:
|
||||||
|
addDisplayMenu(settingsPrefix, "Composite", "", "S-Video", "");
|
||||||
|
addEnhancementsMenu(settingsPrefix, true, false);
|
||||||
|
break;
|
||||||
|
|
||||||
case Analyser::Machine::ZX8081:
|
case Analyser::Machine::ZX8081:
|
||||||
addZX8081Menu(settingsPrefix);
|
addZX8081Menu(settingsPrefix);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Analyser::Machine::ZXSpectrum:
|
||||||
|
addDisplayMenu(settingsPrefix, "Composite", "", "S-Video", "SCART");
|
||||||
|
addEnhancementsMenu(settingsPrefix, true, false);
|
||||||
|
break;
|
||||||
|
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,14 +476,7 @@ void MainWindow::launchMachine() {
|
|||||||
addActivityObserver();
|
addActivityObserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::addDisplayMenu(
|
void MainWindow::addDisplayMenu(const std::string &machinePrefix, const std::string &compositeColour, const std::string &compositeMono, const std::string &svideo, const std::string &rgb) {
|
||||||
const std::string &machinePrefix,
|
|
||||||
const std::string &compositeColour,
|
|
||||||
const std::string &compositeMono,
|
|
||||||
const std::string &svideo,
|
|
||||||
const std::string &rgb,
|
|
||||||
const bool offerDynamicCrop
|
|
||||||
) {
|
|
||||||
// Create a display menu.
|
// Create a display menu.
|
||||||
displayMenu = menuBar()->addMenu(tr("&Display"));
|
displayMenu = menuBar()->addMenu(tr("&Display"));
|
||||||
|
|
||||||
@@ -566,37 +543,9 @@ void MainWindow::addDisplayMenu(
|
|||||||
machine->configurable_device()->set_options(options);
|
machine->configurable_device()->set_options(options);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Possibly add a dynamic crop selector.
|
|
||||||
if(offerDynamicCrop) {
|
|
||||||
displayMenu->addSeparator();
|
|
||||||
|
|
||||||
QAction *const action = new QAction(tr("Crop Dynamically"), this);
|
|
||||||
action->setCheckable(true);
|
|
||||||
displayMenu->addAction(action);
|
|
||||||
|
|
||||||
const auto dynamicCropSettingName = QString::fromStdString(machinePrefix + ".dynamicCrop");
|
|
||||||
if(settings.contains(dynamicCropSettingName)) {
|
|
||||||
const auto useDynamicCrop = settings.value(settingName).toBool();
|
|
||||||
action->setChecked(useDynamicCrop);
|
|
||||||
Reflection::set(*options, Configurable::Options::DynamicCropOptionName, useDynamicCrop);
|
|
||||||
}
|
|
||||||
connect(action, &QAction::toggled, this, [=, this] (const bool ticked) {
|
|
||||||
Settings settings;
|
|
||||||
settings.setValue(dynamicCropSettingName, ticked);
|
|
||||||
|
|
||||||
std::lock_guard lock_guard(machineMutex);
|
|
||||||
auto options = machine->configurable_device()->get_options();
|
|
||||||
Reflection::set(*options, Configurable::Options::DynamicCropOptionName, ticked);
|
|
||||||
machine->configurable_device()->set_options(options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::addEnhancementsMenu(const std::string &machinePrefix, const bool offerQuickLoad, const bool offerQuickBoot) {
|
void MainWindow::addEnhancementsMenu(const std::string &machinePrefix, const bool offerQuickLoad, const bool offerQuickBoot) {
|
||||||
if(!offerQuickLoad && !offerQuickBoot) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
enhancementsMenu = menuBar()->addMenu(tr("&Enhancements"));
|
enhancementsMenu = menuBar()->addMenu(tr("&Enhancements"));
|
||||||
addEnhancementsItems(machinePrefix, enhancementsMenu, offerQuickLoad, offerQuickBoot, false);
|
addEnhancementsItems(machinePrefix, enhancementsMenu, offerQuickLoad, offerQuickBoot, false);
|
||||||
}
|
}
|
||||||
@@ -732,6 +681,9 @@ void MainWindow::toggleAtari2600Switch(const Atari2600Switch toggleSwitch) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::addAppleIIMenu() {
|
void MainWindow::addAppleIIMenu() {
|
||||||
|
// Add the standard display settings.
|
||||||
|
addDisplayMenu("appleII", "Colour", "Monochrome", "", "");
|
||||||
|
|
||||||
// Add an additional tick box, for square pixels.
|
// Add an additional tick box, for square pixels.
|
||||||
QAction *const squarePixelsAction = new QAction(tr("Square Pixels"));
|
QAction *const squarePixelsAction = new QAction(tr("Square Pixels"));
|
||||||
squarePixelsAction->setCheckable(true);
|
squarePixelsAction->setCheckable(true);
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat
|
|||||||
void deleteMachine();
|
void deleteMachine();
|
||||||
|
|
||||||
QMenu *displayMenu = nullptr;
|
QMenu *displayMenu = nullptr;
|
||||||
void addDisplayMenu(const std::string &machinePrefix, const std::string &compositeColour, const std::string &compositeMono, const std::string &svideo, const std::string &rgb, bool allowDynamicCrop);
|
void addDisplayMenu(const std::string &machinePrefix, const std::string &compositeColour, const std::string &compositeMono, const std::string &svideo, const std::string &rgb);
|
||||||
|
|
||||||
QMenu *enhancementsMenu = nullptr;
|
QMenu *enhancementsMenu = nullptr;
|
||||||
QAction *automaticTapeControlAction = nullptr;
|
QAction *automaticTapeControlAction = nullptr;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@
|
|||||||
#ifdef TARGET_QT
|
#ifdef TARGET_QT
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
namespace {
|
namespace {
|
||||||
[[maybe_unused]] QDebug stream(const bool is_info) {
|
QDebug stream(const bool is_info) {
|
||||||
return (is_info ? qInfo() : qWarning()).noquote().nospace();
|
return (is_info ? qInfo() : qWarning()).noquote().nospace();
|
||||||
}
|
}
|
||||||
static constexpr char EndLine = 0;
|
static constexpr char EndLine = 0;
|
||||||
@@ -24,7 +24,7 @@ static constexpr char EndLine = 0;
|
|||||||
#else
|
#else
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
namespace {
|
namespace {
|
||||||
[[maybe_unused]] std::ostream &stream(const bool is_info) {
|
std::ostream &stream(const bool is_info) {
|
||||||
return is_info ? std::cout : std::cerr;
|
return is_info ? std::cout : std::cerr;
|
||||||
}
|
}
|
||||||
static constexpr char EndLine = '\n';
|
static constexpr char EndLine = '\n';
|
||||||
@@ -202,7 +202,8 @@ struct AccumulatingLog {
|
|||||||
template <Source source>
|
template <Source source>
|
||||||
struct LogLine<source, true>: private AccumulatingLog {
|
struct LogLine<source, true>: private AccumulatingLog {
|
||||||
public:
|
public:
|
||||||
explicit LogLine(const bool is_info) noexcept : is_info_(is_info) {}
|
explicit LogLine(bool is_info) noexcept :
|
||||||
|
is_info_(is_info) {}
|
||||||
|
|
||||||
~LogLine() {
|
~LogLine() {
|
||||||
if(output_ == accumulator_.last && source == accumulator_.source && is_info_ == accumulator_.is_info) {
|
if(output_ == accumulator_.last && source == accumulator_.source && is_info_ == accumulator_.is_info) {
|
||||||
@@ -261,7 +262,7 @@ private:
|
|||||||
|
|
||||||
template <Source source>
|
template <Source source>
|
||||||
struct LogLine<source, false> {
|
struct LogLine<source, false> {
|
||||||
explicit LogLine(bool) noexcept {}
|
explicit LogLine(FILE *) noexcept {}
|
||||||
|
|
||||||
template <size_t size, typename... Args>
|
template <size_t size, typename... Args>
|
||||||
auto &append(const char (&)[size], Args...) { return *this; }
|
auto &append(const char (&)[size], Args...) { return *this; }
|
||||||
@@ -278,8 +279,8 @@ public:
|
|||||||
static constexpr bool InfoEnabled = enabled_level(source) == EnabledLevel::ErrorsAndInfo;
|
static constexpr bool InfoEnabled = enabled_level(source) == EnabledLevel::ErrorsAndInfo;
|
||||||
static constexpr bool ErrorsEnabled = enabled_level(source) >= EnabledLevel::Errors;
|
static constexpr bool ErrorsEnabled = enabled_level(source) >= EnabledLevel::Errors;
|
||||||
|
|
||||||
static auto info() { return LogLine<source, InfoEnabled>(true); }
|
static auto info() { return LogLine<source, InfoEnabled>(stdout); }
|
||||||
static auto error() { return LogLine<source, ErrorsEnabled>(false); }
|
static auto error() { return LogLine<source, ErrorsEnabled>(stderr); }
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user