From ef0563f57bc46ae0406052e6d8f993ab5982db22 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Fri, 9 Aug 2019 18:38:22 +0100 Subject: [PATCH] Added multithreaded rendering --- Source/Engine/Bitmap.swift | 4 +-- Source/Engine/Renderer.swift | 20 +++++++++------ Source/Rampage.xcodeproj/project.pbxproj | 4 +++ Source/Rampage/Bitmap+Rendering.swift | 32 ++++++++++++++++++++++++ Source/Rampage/ViewController.swift | 6 ++--- Source/RampageTests/RampageTests.swift | 3 +-- 6 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 Source/Rampage/Bitmap+Rendering.swift diff --git a/Source/Engine/Bitmap.swift b/Source/Engine/Bitmap.swift index ebbab52..b179525 100644 --- a/Source/Engine/Bitmap.swift +++ b/Source/Engine/Bitmap.swift @@ -87,9 +87,9 @@ public extension Bitmap { } mutating func drawImage(_ source: Bitmap, at point: Vector, size: Vector) { - let start = Int(point.x), end = Int(point.x + size.x) + let start = max(0, Int(point.x)), end = Int(point.x + size.x) let stepX = Double(source.width) / size.x - for x in max(0, start) ..< min(width, end) { + for x in start ..< max(start, min(width, end)) { let sourceX = (Double(x) - point.x) * stepX let outputPosition = Vector(x: Double(x), y: point.y) drawColumn(Int(sourceX), of: source, at: outputPosition, height: size.y) diff --git a/Source/Engine/Renderer.swift b/Source/Engine/Renderer.swift index a82fbb4..05f0bd7 100644 --- a/Source/Engine/Renderer.swift +++ b/Source/Engine/Renderer.swift @@ -10,10 +10,15 @@ private let fizzle = (0 ..< 10000).shuffled() public struct Renderer { public private(set) var bitmap: Bitmap + private let width: Int + private let range: Range private let textures: Textures - public init(width: Int, height: Int, textures: Textures) { - self.bitmap = Bitmap(width: width, height: height, color: .black) + public init(width: Int, height: Int, range: Range, textures: Textures) { + self.width = width + self.range = range + let columns = Int(Double(width) * (range.upperBound - range.lowerBound)) + self.bitmap = Bitmap(width: columns, height: height, color: .clear) self.textures = textures } } @@ -21,10 +26,10 @@ public struct Renderer { public extension Renderer { mutating func draw(_ world: World) { let focalLength = 1.0 - let viewWidth = Double(bitmap.width) / Double(bitmap.height) + let viewWidth = Double(width) / Double(bitmap.height) let viewPlane = world.player.direction.orthogonal * viewWidth let viewCenter = world.player.position + world.player.direction * focalLength - let viewStart = viewCenter - viewPlane / 2 + let viewStart = viewCenter - viewPlane * (0.5 - range.lowerBound) // Sort sprites by distance var spritesByDistance: [(distance: Double, sprite: Billboard)] = [] @@ -37,10 +42,9 @@ public extension Renderer { spritesByDistance.sort(by: { $0.distance > $1.distance }) // Cast rays - let columns = bitmap.width - let step = viewPlane / Double(columns) + let step = viewPlane / Double(width) var columnPosition = viewStart - for x in 0 ..< columns { + for x in 0 ..< bitmap.width { let rayDirection = columnPosition - world.player.position let viewPlaneDistance = rayDirection.length let ray = Ray( @@ -120,7 +124,7 @@ public extension Renderer { let screenHeight = Double(bitmap.height) bitmap.drawImage( textures[world.player.animation.texture], - at: Vector(x: Double(bitmap.width) / 2 - screenHeight / 2, y: 0), + at: Vector(x: Double(width) * (0.5 - range.lowerBound) - screenHeight / 2, y: 0), size: Vector(x: screenHeight, y: screenHeight) ) diff --git a/Source/Rampage.xcodeproj/project.pbxproj b/Source/Rampage.xcodeproj/project.pbxproj index 8dfcb18..473d278 100644 --- a/Source/Rampage.xcodeproj/project.pbxproj +++ b/Source/Rampage.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ 01D09B0B22A7F7570052745A /* Textures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D09B0A22A7F7570052745A /* Textures.swift */; }; 01D0F5D922F80E1600682CA1 /* RampageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D0F5D822F80E1600682CA1 /* RampageTests.swift */; }; 01D0F5F122FF095E00682CA1 /* Door.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D0F5F022FF095E00682CA1 /* Door.swift */; }; + 01D0F5EF22FDF19A00682CA1 /* Bitmap+Rendering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D0F5EE22FDF19900682CA1 /* Bitmap+Rendering.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -107,6 +108,7 @@ 01D0F5D822F80E1600682CA1 /* RampageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RampageTests.swift; sourceTree = ""; }; 01D0F5DA22F80E1600682CA1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 01D0F5F022FF095E00682CA1 /* Door.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Door.swift; sourceTree = ""; }; + 01D0F5EE22FDF19900682CA1 /* Bitmap+Rendering.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bitmap+Rendering.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -160,6 +162,7 @@ children = ( 016E41B2228E9A5B00ACF137 /* AppDelegate.swift */, 016E41B4228E9A5B00ACF137 /* ViewController.swift */, + 01D0F5EE22FDF19900682CA1 /* Bitmap+Rendering.swift */, 01D09AF422A482450052745A /* UIImage+Bitmap.swift */, 016E41B6228E9A5B00ACF137 /* Main.storyboard */, 01D09B0022A493A70052745A /* Map.json */, @@ -353,6 +356,7 @@ buildActionMask = 2147483647; files = ( 01D09AF522A482450052745A /* UIImage+Bitmap.swift in Sources */, + 01D0F5EF22FDF19A00682CA1 /* Bitmap+Rendering.swift in Sources */, 016E41B5228E9A5B00ACF137 /* ViewController.swift in Sources */, 016E41B3228E9A5B00ACF137 /* AppDelegate.swift in Sources */, ); diff --git a/Source/Rampage/Bitmap+Rendering.swift b/Source/Rampage/Bitmap+Rendering.swift new file mode 100644 index 0000000..afeffd2 --- /dev/null +++ b/Source/Rampage/Bitmap+Rendering.swift @@ -0,0 +1,32 @@ +// +// ConcurrentRenderer.swift +// Rampage +// +// Created by Nick Lockwood on 09/08/2019. +// Copyright © 2019 Nick Lockwood. All rights reserved. +// + +import UIKit +import Engine + +public extension Bitmap { + init(width: Int, height: Int, world: World, textures: Textures) { + let cpuCores = ProcessInfo.processInfo.activeProcessorCount + var buffers = Array(repeating: [Color](), count: cpuCores) + let step = 1.0 / Double(cpuCores) + DispatchQueue.concurrentPerform(iterations: cpuCores) { i in + let offset = Double(i) * step + let range = offset ..< offset + step + var renderer = Renderer( + width: width, + height: height, + range: range, + textures: textures + ) + renderer.draw(world) + buffers[i] = renderer.bitmap.pixels + } + let pixels = Array(buffers.joined()) + self.init(height: height, pixels: pixels) + } +} diff --git a/Source/Rampage/ViewController.swift b/Source/Rampage/ViewController.swift index 91ccd43..cfca0e2 100644 --- a/Source/Rampage/ViewController.swift +++ b/Source/Rampage/ViewController.swift @@ -86,10 +86,8 @@ class ViewController: UIViewController { lastFrameTime = displayLink.timestamp let width = Int(imageView.bounds.width), height = Int(imageView.bounds.height) - var renderer = Renderer(width: width, height: height, textures: textures) - renderer.draw(world) - - imageView.image = UIImage(bitmap: renderer.bitmap) + let bitmap = Bitmap(width: width, height: height, world: world, textures: textures) + imageView.image = UIImage(bitmap: bitmap) } @objc func fire(_ gestureRecognizer: UITapGestureRecognizer) { diff --git a/Source/RampageTests/RampageTests.swift b/Source/RampageTests/RampageTests.swift index 7504500..01c17bb 100644 --- a/Source/RampageTests/RampageTests.swift +++ b/Source/RampageTests/RampageTests.swift @@ -16,8 +16,7 @@ class RampageTests: XCTestCase { func testRenderFrame() { self.measure { - var renderer = Renderer(width: 1000, height: 1000, textures: textures) - renderer.draw(world) + _ = Bitmap(width: 1000, height: 1000, world: world, textures: textures) } } }