From c5d60184768b6fb971822d58bcdbf99a4e42d750 Mon Sep 17 00:00:00 2001 From: Marc Davis Date: Thu, 10 Apr 2025 21:42:53 -0400 Subject: [PATCH 1/3] added pixelPerfect samplerMode --- DeltaCore/UI/Game/GameView.swift | 36 +++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/DeltaCore/UI/Game/GameView.swift b/DeltaCore/UI/Game/GameView.swift index 54c99fd..e7cae08 100644 --- a/DeltaCore/UI/Game/GameView.swift +++ b/DeltaCore/UI/Game/GameView.swift @@ -32,6 +32,7 @@ public enum SamplerMode { case linear case nearestNeighbor + case pixelPerfect } public class GameView: UIView @@ -75,7 +76,7 @@ public class GameView: UIView switch self.samplerMode { case .linear: image = inputImage.samplingLinear() - case .nearestNeighbor: image = inputImage.samplingNearest() + case .nearestNeighbor, .pixelPerfect: image = inputImage.samplingNearest() } if let filter = self.filter @@ -348,7 +349,20 @@ private extension GameView if let outputImage = self.outputImage { - let bounds = CGRect(x: 0, y: 0, width: self.glkView.drawableWidth, height: self.glkView.drawableHeight) + let x, y, width, height: CGFloat + switch samplerMode { + case .pixelPerfect: + width = floor(CGFloat(self.glkView.drawableWidth) / outputImage.extent.width) * outputImage.extent.width + height = floor(CGFloat(self.glkView.drawableHeight) / outputImage.extent.height) * outputImage.extent.height + x = floor((CGFloat(self.glkView.drawableWidth) - width) / 2) + y = floor((CGFloat(self.glkView.drawableHeight) - height) / 2) + default: + width = CGFloat(self.glkView.drawableWidth) + height = CGFloat(self.glkView.drawableHeight) + x = 0 + y = 0 + } + let bounds = CGRect(x: x, y: y, width: width, height: height) self.openGLESContext.draw(outputImage, in: bounds, from: outputImage.extent) } } @@ -368,8 +382,20 @@ extension GameView: MTKViewDelegate let currentDrawable = self.metalLayer?.nextDrawable() else { return } - let scaleX = view.drawableSize.width / image.extent.width - let scaleY = view.drawableSize.height / image.extent.height + let scaleX, scaleY, offsetX, offsetY: CGFloat + + switch samplerMode { + case .pixelPerfect: + scaleX = floor(view.drawableSize.width / image.extent.width) + scaleY = floor(view.drawableSize.height / image.extent.height) + offsetX = floor((view.drawableSize.width - image.extent.width * scaleX) / 2) + offsetY = floor((view.drawableSize.height - image.extent.height * scaleY) / 2) + default: + scaleX = view.drawableSize.width / image.extent.width + scaleY = view.drawableSize.height / image.extent.height + offsetX = 0 + offsetY = 0 + } let outputImage = image.transformed(by: CGAffineTransform(scaleX: scaleX, y: scaleY)) do @@ -383,7 +409,7 @@ extension GameView: MTKViewDelegate return texture } - try self.metalContext.startTask(toRender: outputImage, from: outputImage.extent, to: destination, at: .zero) + try self.metalContext.startTask(toRender: outputImage, from: outputImage.extent, to: destination, at: CGPoint(x: offsetX, y: offsetY)) commandBuffer.present(currentDrawable) commandBuffer.commit() From 790c42b6ae51bf52ba90da31c238c883bf26e2fd Mon Sep 17 00:00:00 2001 From: Marc Davis Date: Thu, 10 Apr 2025 23:02:26 -0400 Subject: [PATCH 2/3] fixed a misalignment due to the View being at an off-pixel location --- DeltaCore/UI/Game/GameView.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/DeltaCore/UI/Game/GameView.swift b/DeltaCore/UI/Game/GameView.swift index e7cae08..9abce67 100644 --- a/DeltaCore/UI/Game/GameView.swift +++ b/DeltaCore/UI/Game/GameView.swift @@ -126,6 +126,7 @@ public class GameView: UIView private var didLayoutSubviews = false private var didRenderInitialFrame = false private var isRenderingInitialFrame = false + private var pixelAlignmentOffset = CGPointZero private var isUsingMetal: Bool { let isUsingMetal = (self.eaglContext == nil) @@ -155,7 +156,7 @@ public class GameView: UIView } private func initialize() - { + { self.glkView.frame = self.bounds self.glkView.autoresizingMask = [.flexibleWidth, .flexibleHeight] self.glkView.delegate = self.glkViewDelegate @@ -188,6 +189,8 @@ public class GameView: UIView public override func layoutSubviews() { super.layoutSubviews() + self.pixelAlignmentOffset.y = ceil(self.frame.minY) - self.frame.minY + self.pixelAlignmentOffset.x = ceil(self.frame.minX) - self.frame.minX if self.outputImage != nil { @@ -363,6 +366,7 @@ private extension GameView y = 0 } let bounds = CGRect(x: x, y: y, width: width, height: height) + self.glkView.layer.frame.origin = pixelAlignmentOffset self.openGLESContext.draw(outputImage, in: bounds, from: outputImage.extent) } } @@ -390,6 +394,7 @@ extension GameView: MTKViewDelegate scaleY = floor(view.drawableSize.height / image.extent.height) offsetX = floor((view.drawableSize.width - image.extent.width * scaleX) / 2) offsetY = floor((view.drawableSize.height - image.extent.height * scaleY) / 2) + self.metalLayer?.frame.origin = self.pixelAlignmentOffset default: scaleX = view.drawableSize.width / image.extent.width scaleY = view.drawableSize.height / image.extent.height From 3945e673cd3309361d727b163e8453f4034ec8bc Mon Sep 17 00:00:00 2001 From: Marc Davis Date: Thu, 10 Apr 2025 23:13:38 -0400 Subject: [PATCH 3/3] fixed the alignment issue in OpenGL as well as Metal --- DeltaCore/UI/Game/GameView.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/DeltaCore/UI/Game/GameView.swift b/DeltaCore/UI/Game/GameView.swift index 9abce67..d248c7a 100644 --- a/DeltaCore/UI/Game/GameView.swift +++ b/DeltaCore/UI/Game/GameView.swift @@ -127,6 +127,7 @@ public class GameView: UIView private var didRenderInitialFrame = false private var isRenderingInitialFrame = false private var pixelAlignmentOffset = CGPointZero + private var lastGLKOffset = CGPointZero private var isUsingMetal: Bool { let isUsingMetal = (self.eaglContext == nil) @@ -359,6 +360,12 @@ private extension GameView height = floor(CGFloat(self.glkView.drawableHeight) / outputImage.extent.height) * outputImage.extent.height x = floor((CGFloat(self.glkView.drawableWidth) - width) / 2) y = floor((CGFloat(self.glkView.drawableHeight) - height) / 2) + if lastGLKOffset != pixelAlignmentOffset { + DispatchQueue.main.async { + self.glkView.frame.origin = self.pixelAlignmentOffset + self.lastGLKOffset = self.glkView.frame.origin + } + } default: width = CGFloat(self.glkView.drawableWidth) height = CGFloat(self.glkView.drawableHeight) @@ -366,7 +373,6 @@ private extension GameView y = 0 } let bounds = CGRect(x: x, y: y, width: width, height: height) - self.glkView.layer.frame.origin = pixelAlignmentOffset self.openGLESContext.draw(outputImage, in: bounds, from: outputImage.extent) } }