From bcaceff3780221e001fb04c3a28698905754506e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Sep 2020 20:32:58 -0400 Subject: [PATCH] Simplifies in-Metal transform logic, loading responsibility for setup onto the CPU. I think I've also finally excised whatever order-of-operations issue I was having with regard to non-4:3 displays. --- .../Clock Signal/ScanTarget/CSScanTarget.mm | 53 ++++++++++++++----- .../Clock Signal/ScanTarget/ScanTarget.metal | 15 ++---- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm b/OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm index cef6f5e57..f757c18c4 100644 --- a/OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm +++ b/OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm @@ -113,9 +113,7 @@ struct Uniforms { float cyclesMultiplier; float lineWidth; - float aspectRatioMultiplier; - float zoom; - simd::float2 offset; + simd::float3x3 sourcetoDisplay; HalfConverter toRGB; HalfConverter fromRGB; @@ -505,18 +503,49 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget; - (void)setAspectRatio { const auto modals = _scanTarget.modals(); const auto viewAspectRatio = (_view.bounds.size.width / _view.bounds.size.height); + simd::float3x3 sourceToDisplay{1.0f}; - // Set the aspect ratio multiplier. - uniforms()->aspectRatioMultiplier = float(modals.aspect_ratio / viewAspectRatio); + // The starting coordinate space is [0, 1]. - // Also work out the proper zoom. - const double fitWidthZoom = (viewAspectRatio / modals.aspect_ratio) / modals.visible_area.size.width; - const double fitHeightZoom = 1.0 / modals.visible_area.size.height; - uniforms()->zoom = float(std::min(fitWidthZoom, fitHeightZoom)); + // Move the centre of the cropping rectangle to the centre of the display. + { + simd::float3x3 recentre{1.0f}; + recentre.columns[2][0] = 0.5f - (modals.visible_area.origin.x + modals.visible_area.size.width * 0.5f); + recentre.columns[2][1] = 0.5f - (modals.visible_area.origin.y + modals.visible_area.size.height * 0.5f); + sourceToDisplay = recentre * sourceToDisplay; + } - // Store the offset. - uniforms()->offset.x = -modals.visible_area.origin.x; - uniforms()->offset.y = -modals.visible_area.origin.y; + // Convert from the internal [0, 1] to centred [-1, 1] (i.e. Metal's eye coordinates, though also appropriate + // for the zooming step that follows). + { + simd::float3x3 convertToEye; + convertToEye.columns[0][0] = 2.0f; + convertToEye.columns[1][1] = -2.0f; + convertToEye.columns[2][0] = -1.0f; + convertToEye.columns[2][1] = 1.0f; + convertToEye.columns[2][2] = 1.0f; + sourceToDisplay = convertToEye * sourceToDisplay; + } + + // Determine the correct zoom level. This is a combination of (i) the necessary horizontal stretch to produce a proper + // aspect ratio; and (ii) the necessary zoom from there to either fit the visible area width or height as per a decision + // on letterboxing or pillarboxing. + const float aspectRatioStretch = float(modals.aspect_ratio / viewAspectRatio); + const float fitWidthZoom = 1.0f / (float(modals.visible_area.size.width) * aspectRatioStretch); + const float fitHeightZoom = 1.0f / float(modals.visible_area.size.height); + const float zoom = std::min(fitWidthZoom, fitHeightZoom); + + // Convert from there to the proper aspect ratio by stretching or compressing width. + // After this the output is exactly centred, filling the vertical space and being as wide or slender as it likes. + { + simd::float3x3 applyAspectRatio{1.0f}; + applyAspectRatio.columns[0][0] = aspectRatioStretch * zoom; + applyAspectRatio.columns[1][1] = zoom; + sourceToDisplay = applyAspectRatio * sourceToDisplay; + } + + // Store. + uniforms()->sourcetoDisplay = sourceToDisplay; } - (void)setModals:(const Outputs::Display::ScanTarget::Modals &)modals { diff --git a/OSBindings/Mac/Clock Signal/ScanTarget/ScanTarget.metal b/OSBindings/Mac/Clock Signal/ScanTarget/ScanTarget.metal index d2c0cbbe9..88b0b5e99 100644 --- a/OSBindings/Mac/Clock Signal/ScanTarget/ScanTarget.metal +++ b/OSBindings/Mac/Clock Signal/ScanTarget/ScanTarget.metal @@ -21,12 +21,8 @@ struct Uniforms { // This provides the intended height of a scan, in eye-coordinate terms. float lineWidth; - // Provides a scaling factor in order to preserve 4:3 central content. - float aspectRatioMultiplier; - // Provides zoom and offset to scale the source data. - float zoom; - float2 offset; + float3x3 sourceToDisplay; // Provides conversions to and from RGB for the active colour space. half3x3 toRGB; @@ -152,13 +148,8 @@ template SourceInterpolator toDisplay( // Hence determine this quad's real shape, using vertexID to pick a corner. // position2d is now in the range [0, 1]. - float2 position2d = start + (float(vertexID&2) * 0.5f) * tangent + (float(vertexID&1) - 0.5f) * normal * uniforms.lineWidth; - - // Apply the requested offset and zoom, to map the desired area to the range [0, 1]. - position2d = (position2d + uniforms.offset) * uniforms.zoom; - - // Remap from [0, 1] to Metal's [-1, 1] and then apply the aspect ratio correction. - position2d = (position2d * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f)) * float2(uniforms.aspectRatioMultiplier, 1.0f); + 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; output.position = float4( position2d,