• No results found

VS SERIELLT PÅ CPU TO PROCEDURALLY GENERATE A 2D LANDSCAPE IN PARALLELL ON THE

N/A
N/A
Protected

Academic year: 2021

Share "VS SERIELLT PÅ CPU TO PROCEDURALLY GENERATE A 2D LANDSCAPE IN PARALLELL ON THE"

Copied!
38
0
0

Loading.... (view fulltext now)

Full text

(1)

ATT PROCEDURELLT GENERERA ETT

2D LANDSKAP PARALLELLT PÅ GPU

VS SERIELLT PÅ CPU

TO PROCEDURALLY GENERATE A 2D

LANDSCAPE IN PARALLELL ON THE

GPU VS SERIALLY ON THE CPU

Examensarbete inom huvudområdet Informationsteknologi

Grundnivå 30 högskolepoäng

Vårtermin 2019

Björn Wahlberg

(2)

Sammanfattning

Genom att utnyttja procedurella tekniker så skapades en kartgenerator som programmerades att generera skogslandskap. Generatorn använde sig av två olika implementationer, en som körde på centralprocessorn och en som körde på grafikprocessorn. Implementationen som körde på grafikprocessorn använde sig av en shader för att generera kartan. Dessa två implementationer kördes mot varandra i ett antal tester där resultaten visade på att implementationen som körde på grafikprocessorn var överlägset snabbare än den som körde på centralprocessorn. Resultaten visade på en massiv flaskhals när det kom till att kopiera data från grafikminnet till primärminnet. Att undersöka om det går att reducera tiden som kopieringen tar kan vara intressant för framtida forskning.

(3)

Innehållsförteckning

1

Introduktion ... 1

2

Bakgrund ... 2

2.1 PCG tekniker ... 2

2.2 Parallellisering av programkod ... 4

2.3 Simple and Fast Multimedia Library ... 5

(4)

1

Introduktion

Procedurellt genererat innehåll, PCG, förekommer väldigt ofta i spel nu för tiden, mycket för att öka återspelbarheten i ett spel. Några populära exempel på spel som utnyttjar PCG är Terraria(2011) och Minecraft(2011). I takt med att hårdvara blir mer och mer kraftfull så ökar även kraven på spelen som utnyttjar teknikerna eftersom att det går att generera innehåll i realtid. Men finns det outnyttjat potential i grafikkortet? Trenden av ökningen av klockfrekvensen på processorer har reducerats på senare tid, för att istället ersättas av ett större antal kärnor. Här så kan parallellisering av programkod utnyttjas för att utvinna mer ur datorns hårdvara. Ett teknologi-orienterat experiment att utfördes på först en seriell CPU lösning, och sedan en parallell GPU lösning för att undersöka hur lång tid varje metod tog. Detta skedde på varierande stora kartor för att kunna fastställa om det fanns ett samband mellan storlek och tid.

(5)

2

Bakgrund

Procedurell innehållsgenerering, förkortat PCG (Procedural Content Genreation), innebär att ett eller flera aspekt i ett spel genereras automatiskt efter ett bestämt set regler. Sandbox genren utnyttjar ofta denna denna teknik för att skapa världen som spelaren befinner sig i, och används i bl.a. spelen Minecraft (2011) och Terraria (2011). Detta innebär att varje ny värld som skapas kommer att vara unik, förutsatt att ett unikt seed används som input för kartgeneratorn. En stor skillnad mellan Terraria och Minecraft är att världen i Terraria är begränsad, och därmed kan genereras i ett enda svep. I Minecraft så är dock världen teoretiskt sett oändlig, vilket innebär att hela världen ej kan skapas direkt. Där så används istället s.k. ”chunks”, som består av 16 * 16 rutor och är ordnade i ett rutnät, som läses in och ut ur minnet baserat på spelarens position i världen. På så sätt kan bara de chunks som är närmast spelaren genereras om de behövs, eller läsas in från hårddisk om de redan har genererats tidigare. Detta ställer större krav på kartgeneratorn eftersom att nya chunks kommer behöva genereras i realtid. (Mark, Berechet, Mahlmann & Togelius 2015) refererar till generering av kartor i realtid som online generering, medan procedurell generering av kartor under utvecklingsfasen som är hårdkodade kallas offline generering. Det senare ger utvecklarna större kontroll av vad som kommer att finnas i den slutliga produkten. I spelet Borderlands (2009) så används PCG för att generera majoriteten av vapen som spelaren kan hitta och använda, genom att pussla ihop komponenter som t.ex. magasin, sikte och pipa, vilket påverkar vapnets karakteristik och utseende.

Så varför används då PCG? PCG kan användas för att öka ett spels återspelbarhet genom att varje genomspelning kommer att se annorlunda ut. Om ett spel är annorlunda varje gång det spelas, så skulle en person kunna spela spelet teoretiskt sett hur många gånger som helst. Fallout 3 (2008) är ett spel som inte använder sig av någon form av PCG i sina kartor, och innehåller mycket environmental storytelling. Genom att kartan är noggrant handskulpterad så kan nivådesigners uttänkt placera ut ledtrådar åt spelaren att hitta. Detta kan vara extremt tidskrävande, och PCG kan därför användas för att reducera utvecklingsperioden. En nackdel är att denna nivå av environmental storytelling är svår att uppnå med PCG tekniker.

2.1 PCG tekniker

Många algoritmer i PCG använder sig av en noise funktion, som är utvecklad av Ken Perlin (1985) som han anser skapar ”intressanta stokastiska former”. Algoritmen fungerar i n-dimensioner och fungerar kortfattat så att den består av ett rutnät där varje koordinat har en slumpad lutningsvektorkoefficient mellan värdena [-1, 1]. För att räkna ut värdet vid punkt P så beräknas värdet av skalärprodukten mellan lutningsvektorkoefficienten på de hörn som ligger närmast P och avståndsvektorn från varje hörn till P. Algoritmen har en komplexitet på O(2n) där n är antalet dimensioner. Perlin noise har ersatts av simplex noise, även skapat

av Ken Perlin, som har komplexitet O(n2). I Figur 1 så visas ett exempel på hur simplex noise

(6)

Algoritmen använder absolutvärdet av noise funktionen, vilket skapar vassa kanter, och körs i ett antal oktav. För varje oktav så ökar detaljen, men även beräkningskostnaden.

Figur 1

Exempel på simplex noise i 2D

Rose och Bakaoukas (2016) beskriver en mängd andra noise funktioner och rangordnade dem efter hastighet, kvalité samt minneskonsumption. Hur dessa funktioner rangordnats kan ses i Tabell 1.

Tabell 1 En mängd noise algoritmer rangordnade enligt Rose och Bakaoukas (2016)

Algoritm Hastighet Kvalité Minneskrav

Diamond-Square Algorithm

Väldigt snabb Medel Hög

Value Noise Långsam – Snabb* Låg – Medel* Väldigt Låg

Perlin Noise Medel Hög Låg

Simplex Noise Medel** Väldigt Hög Låg

Worley Noise Variabel Unik Variabel

*Beror på vilken interpoleringsfunktion som används **Skalar bättre in i högre dimensioner är Perlin Noise

(7)

till att åstadkomma mer jämn slumpad distribution av föremål som t.ex. träd. Om varje objekt placeras i mitten av en region istället för på de slumpade punkterna så kommer det att bli färre kluster och mindre tomma ytor, jämfört med att direkt placera ut alla objekt.

2.2 Parallellisering av programkod

Gordon E. Moore (1965) förutspådde att antalet transistorer som får plats på ett mikrochip kommer att öka exponentiellt varje år. Denna förutsägelse varit väldigt nära inpå hur verkligheten ser ut. Transistorer har på senare år blivit så pass små att utvecklingen nu har börjat stagnera. Denna utveckling har lett till att. I takt med att beräkningskraften har ökat så har även applikationers krav ökat. Med tanke på att nästan alla persondatorer idag har ett dedikerat grafikkort så kan det uppstå en flaskhals mellan centralprocessorn och grafikkortet, då arbetslasten ojämnt fördelas.

Till skillnad från en modern konsumentprocessor som kan ha upp emot 8 kärnor så har grafikkort idag tusentals kärnor, som parallellt kan utföra instruktioner. Dessa kärnor används normalt sett till att uppdatera geometri samt beräkna pixel (eller fragment) och vertex shaders i spel, men kan även programmeras till att utföra mer generella beräkningar, vilket kallas ”General Purpose Computing on Graphics Processing units”, förkortat GPGPU. För att kunna utnyttja alla dessa kärnor på ett lämpligt sett inom GPGPU programmering så måste program kunna utnyttja parallellisering till en hög grad. Parallellism innebär att flera rader kod kan exekveras parallellt i ett program, och medför en del risker. Om flera trådar vill åt och ändra samma resurs så kan fel uppstå eftersom att instruktionspipelinen fungerar så att tråden först läser in värdet på variabeln, sedan gör ändringar och sist skriver in det nya värdet. Så om två trådar skulle vilja komma åt samma resurs kan det se ut som i Tabell 2. Detta kan lösas genom att användas s.k. mutex lås vid kritiska sektioner, som pausar processers vidare exekvering till dess att låset frigörs. Däremot så lider inte shaderkod av detta problem då varje pixel eller vertex är oberoende av sin omgivning.

Tabell 2 Ett exempel på ett fel p.g.a. race conditions

Process 1 Process 2 Heltalsvärde

Läs värde 0 Läs värde 0 Öka värde 0 Öka Värde 0 Skriv värde 1 Skriv värde 1

(8)

underlättar vid parallell programmering, men som bara kan köras på Nvidia GPUer. OpenCL, ursprungligen utvecklat av Apple, och senare underhållet av Khronos, är ett open source svar på CUDA och är kompatibelt med ett större utbud av hårdvara.

Blewitt, Ushaw och Morgan (2013) menar att utnyttjande av GPGPU kan kräva stora mängder kopiering till och från grafikminne, speciellt om centralprocessorn förväntas utföra beräkningar på resultatet. Detta kan leda till icke-trivial omkostnad (overhead).

2.3 Simple and Fast Multimedia Library

(9)

3

Problemformulering

Att genera kartor procedurellt tar lång tid, och det föreslås därför här att GPGPU programmering kan användas för att effektivisera den tid som det tar. Genom att implementera algoritmer som kan utnyttja hög parallellism så bör problemet kunna lösas på kortare tid. Så genom att testa att generera samma karta på både GPU och CPU så kan resultaten sedan jämföras. En 2D karta kan effektivt ses som en tvådimensionell textur, där varje pixel representerar en kartkoordinat. Kartan består av ett rutnät där varje ruta består av en markhöjd, ett eller inget föremål och en terrängtyp.

Som hypotes så föreslås här att det går att vinna tid på att använda en parallell lösning som körs på en GPU, jämfört med en seriell lösning som körs på en CPU.

3.1 Metodbeskrivning

Eftersom att vi här hade två olika fall som skulle testas mot varandra så föreslogs att en implementation av både GPU och CPU metoden implementeras för att köras i ett

teknologi-orienterat experiment (Wohlin, Runeson, Höst, Ohlsson, Regnell, Wesslén 2012). Detta var

lämpligt eftersom att människor har en tendens att vara partiska, vilket maskiner inte är. Genom att använda CPU metoden som kontroll nivå så kunde GPU metoden utvärderas utifrån den. Om vi varierar storleken på kartan så kunde vi få fram om det fanns ett förhållande mellan storlek och tid mellan de två scenarierna. Ett problem som kan uppstå är dock att parallelliserade program ej är deterministiska då ordningen som processerna kör kommer att variera mellan körningar, vilket i sin tur kan leda till olika resultat. På grund av detta fenomen så kan det vara bra att köra varje test flera gånger för att få fram en median samt max och min varians. Anledningen till att median användes över medel är för att median resultat påverkas mindre av extrema utstickare än medel. Ett annat problem enligt Blewitt, Ushaw och Morgan (2013) kan vara att centralprocessorn inte har tillgång till grafikminnet, vilket leder till att minnet behöver överföras från grafik till primärminne, vilket kan leda till en stor tidsförlust. Även här var det intressant att mäta hur lång tid det tog att överföra olika stora kartor mellan primär- och grafikminne.

Då procedurell generation bör ses som automation så förekommer samma etiska risker som i andra sektorer: att arbetstillfällen försvinner eller omvandlas (le Roux 2018), vilket leder till att arbetare kan behöva omskola sig.

De genererade kartorna kommer att vara tvådimensionella och efterlikna nordiska skogslandskap där det kommer att finnas sjöar, berg, stenar, buskar och träd. Självaste topografin att använde en ”ridged multifractal” algoritm eftersom att de skapar mer trovärdiga landskap än att endast använda noise algoritmer. Voronoi diagram användes för att distribuera stenarna, buskarna och träden

(10)

generation process to achieve just the style of cave that fits a specific application and style.

Mark, Berechet, Mahlmann & Togelius, 2015, s. 2

(11)

4

Genomförande

Det första som behövde implementeras var en datastruktur för kartdatan. Detta gjorde det lättare att se att algoritmerna presterar som förväntat, och att se till att kartgeneratorn genererade lämpliga kartor. Denna struktur kommer att se ut som följande: En Map klass som beskriver kartan i sin helhet. Denna bestog av x * y MapChunks, vilket kartan var indelad i. Dessa chunks bestog i sin tur av Tiles vilka innehöll data om hur de ser ut, alltså markhöjd, marktyp och innehavande objekt, där 0 = inget objekt.

Det var till en början tänkt att GPU varianten skulle använda den inbyggda noise algoritmen i GLSL, men då det gav kompileringsfel så behövde en egen variant implementeras (Gonzales-Vivo 2014). CPU varianten använde dock en annan implementation (Eshelman 2012) då GPU varianten gav felaktiga resultat vid försök att anpassa den till C++. Hur implementationerna skiljer sig åt kan ses i Appendix A - .

Ridged multifractal algoritmen är baserad på Ebert et al. (2002), och Figur 2 visar hur detta kan se ut, där den blåa färgkanalen användes för att spara höjden. Terrängtypen var baserad på höjdvärdet från ridged multifractal algoritmen, och kan se ut som i Figur 3, där den gröna färgkanalen användes och varje nyans motsvarar en terrängtyp.

(12)

Figur 3 Exempel på färgvärden för terrängtyp

Voronoi algoritmen byggde mycket på Quilez (2013), och har anpassats efter problemet. Genom att anpassa parametrarna så färgas endast en enstaka eller ett fåtal pixlar runt mitten av punkterna, vilket användes för att bestämma vart objekt skulle placeras. Figur 4 visar hur detta kan se ut, och vilken typ av objekt som placeras ut bestäms av en pseudo slumpad funktion, vilket sparades i den röda färgkanalen. Både GPU och CPU implementationerna använde samma voronoi implementation, anpassade för C++ samt GLSL.

Figur 4 Exempel på distribution av objekt

(13)

strategispel, till skillnad från Terraria som är en platformer, och är genomskuret likt en myrfarm. Efter att kartan hade genererats i bildform så var bilden tvungen att flyttas till primärminnet där centralprocessorn kunde läsa det. Detta är en väldigt tung handling som tyvärr krävdes och kan leda till stora tidsförluster. Det var därför intressant att mäta hur lång tid denna överföring tog i förhållande till den totala tiden som det tog för en karta att genereras.

En naiv lösning på shadern vore att generera en textur för varje variabel, men detta skulle kräva ett pass för varje variabel, och ta tre gånger så lång tid jämfört med att bara köra ett pass. Istället så kan detta ske genom att begränsa storleken på varje variabel och sedan bit-maska ihop alla variabler till 32 bitar. Eftersom att varje pixel består av 32 bitar och vi har tre olika parametrar så behövde datan anpassas till denna begränsning. Det var till en början tänkt att de sista 8 bitarna i texturen som är alfa värde skulle användas för att öka de möjliga värden på höjden till 2^16 = 65536 värden, men i och med att alfa värdet ändras så påverkades även de övriga färgvariablerna, så därför var ändast 24 bitar användbara, och alfan kommer att vara konstant. Genom att dela upp de 24 bitarna enligt Figur 5, så räckte det med att endast köra ett pass med shadern, och ett maxvärde på 2^8 = 256 räckte givet problemets krav, samt gav mycket utrymme för att lägga till mer variation i framtiden. Om kraven ändras så går det att ändra distributionen för att flytta över extra bitar från andra attribut (t.ex. 7, 7, 9 bitar). En nackdel med denna lösning var att den var mer begränsad till skillnad från CPU lösningen, vilket kan göra det intressant att undersöka även en trepass lösning, för att jämföra med CPU lösningen.

Figur 5 Ett exempel på hur datan kan delas upp

(14)

Figur 6 Illustration av hur ordningen av pixlar och alla tiles skiljer sig åt; till

vänster är ordningen enligt en textur och höger det interna formatet som

problemlösningen använder

Shadertoy (Beatypi 2018) var en stor tillgång under utvecklingen av shaderkoden, då det är enkelt och snabbt att kompilera kod samt testa idéer. Det finns även mycket inspiration att hämta från det bibliotek av befintliga shaders som folk har laddat upp på sidan. Vissa ändringar behövde göras mellan Shadertoy och SFML’s kompilator då funktionskallen och variablerna skiljde sig åt aningen. Mycket huvudvärk under utvecklingen kom från det faktum att SFML var väldigt dåligt med att ge kompileringsfelsmeddelanden, utan för det mesta bara optimerar bort rader kod som inte används. P.g.a. detta så optimerade försök att kalla på ”noise2” funktionen i GLSL API’n bort. När alla resultat från de olika kanalerna lades ihop så kan det se ut som i Figur 7. Eftersom att terrängtyp endast var beroende av höjd så var den blåa och gröna kanalen nästan identiska.

(15)

Då SFML har stöd för att rita ut grafik så kunde detta användas för att lätt validera kartdatan visuellt. I Figur 8 så kan ett exempel ses på hur det kan se ut. Detta exempel hade väldigt få objekt utplacerade. Ljusare färger på rutorna innebär att de är högre upp, och de förskjuts vertikalt uppåt ett par pixlar baserat på höjdvärdet, vilket skapade de svarta områdena som representerar höjdskillnader.

Figur 8 Visuell representation av ett styckprov av en karta

Då den enda parametern i shadern som påverkade körtiden var antal oktav så gjordes ett mindre test för att komma fram till ett lämpligt värde. Ett lämpligt värde var ett värde där körtiden inte påverkades allt för mycket samtidigt som kartkvalitén inte påverkades alltför negativt. I Figur 9 så visas resultatet från varje test i bildformat, och värdet 4 valdes för oktav då kvalitén endast ökar marginellt vid högre värden.

(16)

5

Utvärdering

Här följer resultaten av de test som kördes på de två separata maskinerna med olika hårdvarukonfigurationer, vilka hädanefter refereras till som M1 samt M2, vars hårdvarukonfiguration kan ses i Tabell 3. De två datorerna var en desktop och en laptop. Den fullständiga testdatan finns tillgänglig i Appendix B - och följande är endast utvalda representationer avsedda att framlyfta relevanta resultat. All tid är i mikrosekunder.

Tabell 3 Hårdvarukonfigurationen på de två maskinerna

Maskin referens M1 M2

Operativsystem Windows 10 64-bit Windows 7 64-bit Centralprocessor AMD Ryzen 52600X six-core @

4.05 GHz

Intel Core i5-4210U @ 1.70GHz Grafikkort AMD Radeon RX 580 Intel Graphics 4400

Primärminne 16GB DDR5 RAM 8GB DDR3 RAM

5.1 Presentation av undersökning

0 2000000 4000000 6000000 8000000 10000000 12000000 14000000 800 1200 1600 2000 2400 2800 3200 3600 4000 4400 4800 5200 5600 6000 6400 6800 7200 7600 8000 8400

Total tid GPU Total tid CPU

Rutor

(17)

0 5000000 10000000 15000000 20000000 800 1200 1600 2000 2400 2800 3200 3600 4000 4400 4800 5200 5600 6000 6400 6800 7200 7600 8000 8400

Total tid GPU Total tid CPU

Rutor

Figur 11 Medianresultaten på M2

0 50000 100000 150000 200000 250000 300000 800 1200 1600 2000 2400 2800 3200 3600 4000 4400 4800 5200 5600 6000 6400 6800 7200 7600 8000 8400 GPU Min GPU Med GPU Max Rutor

Figur 12 Varians av GPU resultaten på M1

0 500000 1000000 1500000 2000000 2500000 800 1200 1600 2000 2400 2800 3200 3600 4000 4400 4800 5200 5600 6000 6400 6800 7200 7600 8000 8400 GPU Min GPU Med GPU Max Rutor

(18)

0 2000000 4000000 6000000 8000000 10000000 12000000 14000000 16000000 800 1200 1600 2000 2400 2800 3200 3600 4000 4400 4800 5200 5600 6000 6400 6800 7200 7600 8000 8400 CPU Min CPU Med CPU Max Rutor

Figur 14 Varians av CPU resultaten på M1

0 5000000 10000000 15000000 20000000 25000000 800 1200 1600 2000 2400 2800 3200 3600 4000 4400 4800 5200 5600 6000 6400 6800 7200 7600 8000 8400 CPU Min CPU Med CPU Max Rutor

Figur 15 Varians av CPU resultaten på M2

0 2000000 4000000 6000000 8000000 10000000 12000000 800 1200 1600 2000 2400 2800 3200 3600 4000 4400 4800 5200 5600 6000 6400 6800 7200 7600 8000 8400 GPU Max CPU Min Rutor

(19)

0 5000000 10000000 15000000 20000000 800 1200 1600 2000 2400 2800 3200 3600 4000 4400 4800 5200 5600 6000 6400 6800 7200 7600 8000 8400 GPU Max CPU Min Rutor

Figur 17 Skillnad mellan GPU max och CPU min på M2

0 10 20 30 40 50 60 70 800 1600 2400 3200 4000 4800 5600 6400 7200 8000

CPU/GPU median förhållande (M1) CPU/GPU median förhållande (M2)

Rutor

Figur 18 Förhållande mellan CPU och GPU på båda maskinerna

0 5000 10000 15000 20000 800 1600 2400 3200 4000 4800 5600 6400 7200 8000

GPU Gen median (M1) GPU Gen median (M2)

Rutor

(20)

Tabell 4 Tabell över diverse median resultat på M1 över och M2 under Antal Tiles 800 1 200 1 600 2 000 2 400 2 800 3 200 GPU Generering 538 615 780 829 696 741 812 GPU kopiering 12 985 25 615 36 527 48 132 61 441 67 152 75 332 GPU Total 13 523 26 230 37 307 48 961 62 137 67 893 76 144 CPU Total 472 838 1 153 057 1 732 365 2 036 663 2 787 766 3 211 731 3 868 719 Antal Tiles 800 1 200 1 600 2 000 2 400 2 800 3 200 GPU Generering 307 235 294 312 300 294 289 GPU kopiering 16 494 31 762 48 221 65 254 82 462 101 706 132 475 GPU Total 16 801 31 999 48 515 65 566 82 762 102 000 132 766 CPU Total 952 444 1 761 948 2 652 508 3 593 297 4 363 188 5 512 017 7 318 153

5.2 Analys

Som Figur 12 visar så tar själva genereringen av kartorna genererade på en GPU bara en fraktion av tiden som det tar att generera på en CPU, och det är självaste kopieringen från grafikminne till primärminne som tar majoriteten av tiden. Om det skulle gå att få ner denna tid så skulle vinsterna vara ännu större. Trots detta så är vinsterna vid användning av GPU fortfarande i vissa fall bara en femtiondel av när CPU används. Med tanke på hur minimalistisk generatorn som skapats är så skulle tidsåtgången knappt öka om komplexiteten på generatorn ökar, förutsatt att flera variabler ej används, då det skulle öka tiden för kopieringsdelen. Enligt Figur 19 så presterar M2 bättre än M1 i alla testfall när det kommer till att generera kartorna, trots att M1 har bättre grafikprestanda än M2. Det är endast i kopieringen till primärminne som M1 kommer ikapp, då tiden som kartgenereringen tar i förhållande till kopieringen är trivial.

5.3 Slutsatser

(21)
(22)

6

Avslutande diskussion

6.1 Sammanfattning

Genom att utnyttja de procedurella teknikerna simplex noise, ridged multifractal samt Voronoi diagram så skapades en minimalistisk kartgenerator avsedd att kunna köra tester på. Generatorn har två olika implementationer, en för varje test, där den ena körs på centralprocessorn och den andra på grafikprocessorn. Implementationen som kör på grafikprocessorn använder sig av en shader i grafikbiblioteket SFML, där varje pixel i en bild motsvarar varje tile i kartan. Testerna mäter hur lång tid som varje metod tar för en linjärt storleksökande karta. Det kördes totalt 20 tester där varje test kördes 15 gånger, och medianen samt min och max värden för varje metod sparades ned i en fil. Testerna kördes på två olika datorer med olika hårdvara, en stationär och en laptop. Då den enda parametern i generatorn som påverkar tidsåtgången är antal oktav i ridged multifractal algoritmen så kördes ett mindre test för att komma fram till ett lämpligt värde. Det visade sig att kvalitén på kartorna knapp ökade efter 4 oktav, och det värdet användes därför.

Testerna visade på att det var överlägset snabbare att köra metoden som utnyttjar grafikkortet. Det visade sig även att självaste kopieringen från grafikminne till primärminne var vad som tog längst tid i generatorn, då självaste kartgenereringen bara tog en bråkdel av den totala tiden.

6.2 Diskussion

Trots en viss mängd varians på min/max resultaten, speciellt på M2 enligt Figur 13, så visade båda maskinerna på en relativt linjär kurva när medianvärdet användes. Denna varians berodde mest troligen på att grafikprocessorn eller centralprocessorn var upptagna med annat då testen kördes på en Windows maskin med andra processer i bakgrunden som kan ha tagit upp processorernas schematid. De avvikande resultaten kan möjligen ha undvikts om flera iterationer körts på varje test. Trots detta så kom aldrig det lägsta resultatet på CPU upp till det högsta resultatet på GPU förutom i ett extremfall på test 1 på M2 vilket kan ses i Figur 17. Det faktum att de olika implementationerna använde olika simplex noise funktioner kan ha påverkat resultaten negativt, och lett till en viss mängd artificiell inflation. Trots att maskiner är opartiska så är programmerarna som programmerar dem ej det. Även om testet/implementationen är partiska så är det osannolikt att en opartisk lösning skulle generera negativa resultat med tanke på hur ensidiga resultaten ser ut i Figur 10 och Figur 11.

Likt Trapnell och Schats (2009, s429) så reducerades här den totala tiden som beräkningarna tog när en parallelliserad lösning implementerats på en GPU. Anledningen till att vinsterna är så pass mycket större här beror troligtvis på att problemet som presenterats här har mycket högre nivå av beläggning (occupancy), eller partiska implementationer av de procedurella algoritmerna.

(23)

att generera och kopiera över en karta av storleken 8400 tiles, vilket skulle kunna ta en människa upp till många timmar att åstadkomma. Energikonsumtionen för detta, en människa som sitter flera timmar i en grafisk kartredigerare, bör konsumera mer ström än en kartgenerator som endast kör i 0.2s. Detta leder in i nästa punkt vilken är automation. Eftersom att det inte längre krävs en människa som handskulpterar hela världen så försvinner det arbetstillfället. Från en annan synvinkel så frigörs nivådesignern som hade ansvar för att designa kartutseendet att utföra andra arbetsuppgifter, eller att polera och anpassa en karta som genererats antingen utav av en procedurell generator eller utifrån geospatial data. Att konfigurera kartgeneratorns parametrar skulle t.ex. kunna vara ytterligare en av dessa nya arbetsuppgifter. Detta skulle i sin tur kunna leda till en översaturerad spelmarknad. En översaturerad spelmarknad är något vi ser mer och mer idag, troligtvis till följd av att spel blir mer och mer accepterat kulturellt, men även på grund av att ingångsbarriären minskat avsevärt då tillgången till spelutvecklingsmiljöer ökat eftersom att Unity och Unreal Engine är gratis.

6.3 Framtida arbete

En av de mest intressanta upptäckterna i denna studie har varit att det som tar upp majoriteten av tiden i GPU lösningen överlägset varit kopieringen från grafikminne till primärminne, vilket kan ses i Tabell 4. Blewitt, Ushaw och Morgan(2013, s265) menar att omkostnaden för att kopiera data mellan primär och grafikminne är icke-trivial, vilket stämmer överens med upptäckterna i denna studie. Det kan därför vara intressant att undersöka en lösning som använder sig av OpenCL istället för, eller i kombination med en shader. Genom att hitta sett att reducera tiden som kopieringen tar så skulle vinsterna kunna vara ännu större. En möjlig aveny för det är genom att undersöka lösningar med OpenCL eller CUDA. Alternativt så skulle lösningar som utnyttjar flera trådar på en CPU där kopieringen sker kunna undersökas för att få ner kopieringstiden.

Kartgeneratorn som används i denna studie är ytterst minimalistisk, och det kan vara värdefullt att undersöka om liknande resultat visar sig i en mer omständlig kartgenerator. Eftersom att det element som tog längst tid på GPU lösningen var kopieringen från grafikminne till primärminne så skulle resultatet troligen bli ännu mer ensidigt om komplexiteten fokuserade på att förbättra placeringen av terräng, som att t.ex. lägga till stränder mellan landmassa och vatten, vilket skulle kunna ske i ett andra pass. Då självaste genereringen av kartan på GPU tog ytterst lite tid så skulle tiden knappt påverkas i GPU lösningen. Om däremot flera komponenter som kräver mer utdata att överföra används så skulle sannolikt resultatet gå mer åt CPU lösningens håll.

Eftersom att spel mer och mer rör sig mot den mobila marknaden så kan det även finnas intresse för att undersöka hur pass effektivt det är att utnyttja parallella lösningar på hårdvaran som mobiler använder. Här så är energieffektivitet ännu viktigare då reducerad energikonsumtion leder till längre batteritid, vilket är fördelaktigt för mobila enheter.

Syftet med denna undersökning var att undersöka om en parallelliserad GPU lösning var snabbare än en seriell CPU lösning. Det kan vara värdefullt att inkludera en tredje testfall som använder sig av en parallelliserad CPU lösning. Skulle en sådan lösning kunna mäta sig, eller till och med överta GPU lösningen?

(24)
(25)

Referenser

Beautypi (2018) Shadertoy (Version 0.9.9)[Datorprogram]. Beautypi. Tillgängligt på Internet: https://www.shadertoy.com/

Bethesta Game Studios (2008) Fallout 3 (Version 1.0) [Datorprogram]. Bethesta Softworks. Tillgängligt på Internet: https://store.steampowered.com/app/22300/Fallout_3/

Blewitt, W., Ushaw, G., Morgan, G. (2013) Applicability of GPGPU Computing to Real-Time AI Solutions in Games. IEEE Transactions on Computational Intelligence and AI in

Games. 5(3), s265–275.

Ebert, D. S., Musgrave, F. K., Peachey. D., Perlin, K. & Worley, S. (2002) Texturing and

Modeling: A Procedural Approach. 3. uppl. Elsevier.

Eshelman, E. (2012) Simplex Noise for C++ and Python. http://www.6by9.net/simplex-noise-for-c-and-python/ [2019-05-27]

Gearbox Software (2009) Borderlands (Version: 1.0) [Datorprogram]. 2K. Tillgänglig på Internet: https://borderlands.com/.

Gonzales-Vivo, P. (2014) GLSL Noise.

https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83 [2019-05-27] Le Roux, D. B. (2018) Automation and employment: the case of South Africa. African

Journal of Science, Technology, Innovation and Development. 10 (4), s.507–517.

Mark, B., Berechet, T., Mahlmann, T. & Togelius, J. ( 2015) Procedural Generation of 3D Caves for Games on the GPU. Foundations of Digital Games. United States, 22 Juni 2015. Mojang (2011) Minecraft (Version: 1.0) [Datorprogram]. Mojang. Tillgänglig på Internet:

https://minecraft.net/.

Moore, G. E. (1998) Cramming More Components onto Integrated Circuits.

PROCEEDINGS- IEEE. 86 (1), s. 82–85.

Perlin, K. (1985) An image synthesizer. ACM SIGGRAPH Computer Graphics. 19 (3), s. 287– 296.

Quilez, I. (2013) Voronoi – basic. https://www.shadertoy.com/view/MslGD8 [2018-5-27] Re-Logic (2011) Terraria (Version: 1.0) [Datorprogram]. Re-Logic. Tillgänglig på Internet:

https://terraria.org/.

Rose, T. J., Bakaoukas, A. G. (2016) Algorithms and Approaches for Procedural Terrain Generation - A Brief Review of Current Techniques. 2016 8th International Conference on Games and Virtual Worlds for Serious Applications (VS-GAMES). Barcelona, Spanien 7-9sept. 2016. doi: 10.1109/VS-GAMES.2016.7590336

(26)

Trapnell, C. & Schatz, M. C. (2009) Optimizing data intensive GPGPU computations for DNA sequence alignment. Parallel Computing. 35(8), s.429–440.

Uenishi, T., Nakashima, T. & Fujimoto, N. (2011) Parallelizing fuzzy rule generation using GPGPU. Artificial Life and Robotics 16. 2(2011), s.214-218.

Wohlin, C., Runeson, P., Höst, M., Ohlsson, M. C., Regnell, B. & Wesslén, A. (2012)

Experimentation in software engineering. Berlin; New York: Springer. doi:

(27)

Appendix A - Noise algoritm implementationerna

GLSL implementation: float noise(vec2 p){ vec2 ip = floor(p); vec2 u = fract(p); u = u*u*(3.0-2.0*u); float res = mix(

mix(hash(ip).x,hash(ip+vec2(1.0,0.0)).x,u.x),

mix(hash(ip+vec2(0.0,1.0)).x,hash(ip+vec2(1.0,1.0)).x,u.x),u.y); return res*res;

}

C++ implementation:

float noise(const float x, const float y) {

// Noise contributions from the three corners

float n0, n1, n2;

// Skew the input space to determine which simplex cell we're in

float F2 = 0.5f * (sqrtf(3.0f) - 1.0f);

// Hairy factor for 2D

float s = (x + y) * F2;

int i = fastfloor(x + s);

int j = fastfloor(y + s);

float G2 = (3.0f - sqrtf(3.0f)) / 6.0f;

float t = (i + j) * G2;

// Unskew the cell origin back to (x,y) space

float X0 = i - t;

float Y0 = j - t;

// The x,y distances from the cell origin

float x0 = x - X0;

float y0 = y - Y0;

// For the 2D case, the simplex shape is an equilateral triangle. // Determine which simplex we are in.

int i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords

if (x0 > y0) { i1 = 1; j1 = 0; } // lower triangle, XY order: (0,0)->(1,0)->(1,1)

else { i1 = 0; j1 = 1; } // upper triangle, YX order: (0,0)->(0,1)->(1,1) // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and

// a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where

// c = (3-sqrt(3))/6

float x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords

float y1 = y0 - j1 + G2;

float x2 = x0 - 1.0f + 2.0f * G2; // Offsets for last corner in (x,y) unskewed coords

float y2 = y0 - 1.0f + 2.0f * G2;

// Work out the hashed gradient indices of the three simplex corners

int ii = i & 255;

(28)

int gi2 = perm[ii + 1 + perm[jj + 1]] % 12;

// Calculate the contribution from the three corners

float t0 = 0.5f - x0 * x0 - y0 * y0;

if (t0 < 0) n0 = 0.0f;

else

{

t0 *= t0;

n0 = t0 * t0 * dot(grad3[gi0], x0, y0); // (x,y) of grad3 used for 2D gradient } float t1 = 0.5f - x1 * x1 - y1 * y1; if (t1 < 0) n1 = 0.0f; else { t1 *= t1; n1 = t1 * t1 * dot(grad3[gi1], x1, y1); } float t2 = 0.5f - x2 * x2 - y2 * y2; if (t2 < 0) n2 = 0.0f; else { t2 *= t2; n2 = t2 * t2 * dot(grad3[gi2], x2, y2); }

// Add contributions from each corner to get the final noise value. // The result is scaled to return values in the interval [-1,1].

(29)

Appendix B - Testresultat

Test 1

Antal test genomförda: 20, 15 iterationer på varje test med 4 oktav (M1). Windows 10

AMD Ryzen 52600X six-core @ 4.05 GHz AMD Radeon RX 580

16GB DDR5 RAM

Test nr 1/20 Size: 800 Tiles GPU:

Min 10852 µs (10346 copy, 506 gen) Max 40201 µs (26209 copy, 13992 gen) Median 13523 µs (12985 copy, 538 gen) CPU:

Min 472838 µs, Max 609401 µs, Median 555062 µs

Test nr 2/20 Size: 1200 Tiles GPU:

Min 22805 µs (22245 copy, 560 gen) Max 35287 µs (25872 copy, 9415 gen) Median 26230 µs (25615 copy, 615 gen) CPU:

Min 926475 µs, Max 1212237 µs, Median 1153057 µs

Test nr 3/20 Size: 1600 Tiles GPU:

Min 27915 µs (27249 copy, 666 gen) Max 40046 µs (39406 copy, 640 gen) Median 37307 µs (36527 copy, 780 gen) CPU:

Min 1396265 µs, Max 1882800 µs, Median 1732365 µs

Test nr 4/20 Size: 2000 Tiles GPU:

(30)

CPU:

Min 1852901 µs, Max 2390244 µs, Median 2036663 µs

Test nr 5/20 Size: 2400 Tiles GPU:

Min 53972 µs (53351 copy, 620 gen) Max 68518 µs (54651 copy, 13866 gen) Median 62137 µs (61441 copy, 696 gen) CPU:

Min 2452552 µs, Max 3002815 µs, Median 2787766 µs

Test nr 6/20 Size: 2800 Tiles GPU:

Min 53090 µs (52433 copy, 657 gen) Max 79047 µs (78389 copy, 658 gen) Median 67893 µs (67152 copy, 741 gen) CPU:

Min 2926200 µs, Max 3723330 µs, Median 3211731 µs

Test nr 7/20 Size: 3200 Tiles GPU:

Min 64204 µs (63329 copy, 875 gen) Max 88564 µs (87674 copy, 889 gen) Median 76144 µs (75332 copy, 812 gen) CPU:

Min 3286222 µs, Max 4285421 µs, Median 3868719 µs

Test nr 8/20 Size: 3600 Tiles GPU:

Min 67753 µs (66965 copy, 787 gen) Max 112752 µs (100792 copy, 11960 gen) Median 87780 µs (86976 copy, 804 gen) CPU:

(31)

GPU:

Min 80971 µs (80023 copy, 948 gen) Max 121041 µs (116676 copy, 4365 gen) Median 110436 µs (109493 copy, 943 gen) CPU:

Min 4224535 µs, Max 5988644 µs, Median 5306501 µs

Test nr 10/20 Size: 4400 Tiles GPU:

Min 87741 µs (86934 copy, 807 gen) Max 133287 µs (132283 copy, 1004 gen) Median 119755 µs (104995 copy, 14760 gen) CPU:

Min 4685450 µs, Max 6501671 µs, Median 5731589 µs

Test nr 11/20 Size: 4800 Tiles GPU:

Min 96453 µs (95591 copy, 861 gen) Max 142438 µs (141345 copy, 1093 gen) Median 135369 µs (134496 copy, 873 gen) CPU:

Min 5342345 µs, Max 7249928 µs, Median 6748539 µs

Test nr 12/20 Size: 5200 Tiles GPU:

Min 110640 µs (109417 copy, 1223 gen) Max 158525 µs (157061 copy, 1463 gen) Median 148697 µs (147729 copy, 968 gen) CPU:

(32)

Test nr 13/20 Size: 5600 Tiles GPU:

Min 114074 µs (113143 copy, 931 gen) Max 169926 µs (154768 copy, 15158 gen) Median 138556 µs (137444 copy, 1112 gen) CPU:

Min 6632380 µs, Max 8793066 µs, Median 7648377 µs

Test nr 14/20 Size: 6000 Tiles GPU:

Min 129000 µs (127628 copy, 1372 gen) Max 181982 µs (180511 copy, 1471 gen) Median 164857 µs (163525 copy, 1332 gen) CPU:

Min 6858088 µs, Max 9044872 µs, Median 8155867 µs

Test nr 15/20 Size: 6400 Tiles GPU:

Min 136973 µs (135457 copy, 1515 gen) Max 202558 µs (201081 copy, 1476 gen) Median 180099 µs (178768 copy, 1331 gen) CPU:

Min 7219580 µs, Max 10230989 µs, Median 9620174 µs

Test nr 16/20 Size: 6800 Tiles GPU:

Min 142799 µs (141670 copy, 1129 gen) Max 214727 µs (199283 copy, 15444 gen) Median 191985 µs (190684 copy, 1301 gen) CPU:

(33)

Test nr 17/20 Size: 7200 Tiles GPU:

Min 167661 µs (166175 copy, 1485 gen) Max 221832 µs (220208 copy, 1624 gen) Median 200425 µs (198837 copy, 1588 gen) CPU:

Min 8860071 µs, Max 11580357 µs, Median 9583147 µs

Test nr 18/20 Size: 7600 Tiles GPU:

Min 170790 µs (169372 copy, 1417 gen) Max 226239 µs (224634 copy, 1605 gen) Median 213421 µs (211114 copy, 2306 gen) CPU:

Min 9005548 µs, Max 11683692 µs, Median 10462251 µs

Test nr 19/20 Size: 8000 Tiles GPU:

Min 175440 µs (173783 copy, 1656 gen) Max 257893 µs (255282 copy, 2610 gen) Median 221380 µs (220070 copy, 1310 gen) CPU:

Min 9299708 µs, Max 13543884 µs, Median 11267603 µs

Test nr 20/20 Size: 8400 Tiles GPU:

Min 181038 µs (179274 copy, 1764 gen) Max 248233 µs (246968 copy, 1264 gen) Median 226910 µs (224741 copy, 2169 gen) CPU:

(34)

Test 2

Antal test genomförda: 20, 15 iterationer på varje test med 4 oktav (M2). Lenovo G50-70, Windows 7

Intel Core i5-4210U @ 1.70GHz Intel Graphics 4400

8GB DDR3 RAM 1600 MHz

Test nr 1/20 Size: 800 Tiles GPU:

Min 14363 µs (14084 copy, 278 gen)

Max 2114501 µs (213265 copy, 1901235 gen) Median 16801 µs (16494 copy, 307 gen) CPU:

Min 861466 µs, Max 969807 µs, Median 952444 µs

Test nr 2/20 Size: 1200 Tiles GPU:

Min 29238 µs (28932 copy, 305 gen) Max 77331 µs (77017 copy, 314 gen) Median 31999 µs (31762 copy, 235 gen) CPU:

Min 1746208 µs, Max 1924594 µs, Median 1761948 µs

Test nr 3/20 Size: 1600 Tiles GPU:

Min 43112 µs (42839 copy, 273 gen) Max 118373 µs (118020 copy, 352 gen) Median 48515 µs (48221 copy, 294 gen) CPU:

Min 2590829 µs, Max 2750142 µs, Median 2652508 µs

Test nr 4/20 Size: 2000 Tiles GPU:

(35)

CPU:

Min 3449742 µs, Max 3812278 µs, Median 3593297 µs

Test nr 5/20 Size: 2400 Tiles GPU:

Min 72454 µs (72022 copy, 432 gen) Max 212171 µs (211837 copy, 334 gen) Median 82762 µs (82462 copy, 300 gen) CPU:

Min 4278352 µs, Max 4870494 µs, Median 4363188 µs

Test nr 6/20 Size: 2800 Tiles GPU:

Min 90062 µs (89736 copy, 316 gen) Max 248201 µs (241390 copy, 6810 gen) Median 102000 µs (101706 copy, 294 gen) CPU:

Min 5189534 µs, Max 5885093 µs, Median 5512017 µs

Test nr 7/20 Size: 3200 Tiles GPU:

Min 112772 µs (112448 copy, 322 gen) Max 369416 µs (361739 copy, 7677 gen) Median 118016 µs (117647 copy, 369 gen) CPU:

Min 6356147 µs, Max 6808252 µs, Median 6569977 µs

Test nr 8/20 Size: 3600 Tiles GPU:

Min 122691 µs (122393 copy, 298 gen) Max 316772 µs (311383 copy, 5389 gen) Median 132766 µs (132475 copy, 289 gen) CPU:

(36)

Test nr 9/20 Size: 4000 Tiles GPU:

Min 134020 µs (133716 copy, 302 gen) Max 362155 µs (356714 copy, 5441 gen) Median 147445 µs (147138 copy, 305 gen) CPU:

Min 7908692 µs, Max 8689653 µs, Median 8314355 µs

Test nr 10/20 Size: 4400 Tiles GPU:

Min 149992 µs (149696 copy, 295 gen) Max 396574 µs (396168 copy, 405 gen) Median 171189 µs (170862 copy, 327 gen) CPU:

Min 8986972 µs, Max 10353637 µs, Median 9600626 µs

Test nr 11/20 Size: 4800 Tiles GPU:

Min 178513 µs (178224 copy, 289 gen) Max 592104 µs (585942 copy, 6162 gen) Median 224697 µs (224212 copy, 485 gen) CPU:

Min 10421648 µs, Max 13385232 µs, Median 12527380 µs

Test nr 12/20 Size: 5200 Tiles GPU:

Min 182539 µs (182087 copy, 452 gen) Max 542291 µs (541883 copy, 408 gen) Median 214024 µs (213694 copy, 330 gen) CPU:

(37)

Test nr 13/20 Size: 5600 Tiles GPU:

Min 213288 µs (212954 copy, 334 gen) Max 520530 µs (520149 copy, 381 gen) Median 224051 µs (223561 copy, 489 gen) CPU:

Min 12331247 µs, Max 13178677 µs, Median 12573967 µs

Test nr 14/20 Size: 6000 Tiles GPU:

Min 189837 µs (189542 copy, 294 gen) Max 565266 µs (564750 copy, 514 gen) Median 224033 µs (223706 copy, 327 gen) CPU:

Min 12113232 µs, Max 14253186 µs, Median 13094346 µs

Test nr 15/20 Size: 6400 Tiles GPU:

Min 205497 µs (205207 copy, 290 gen) Max 640778 µs (640436 copy, 342 gen) Median 233690 µs (233237 copy, 452 gen) CPU:

Min 12991103 µs, Max 15812751 µs, Median 13288832 µs

Test nr 16/20 Size: 6800 Tiles GPU:

Min 224852 µs (224563 copy, 289 gen) Max 717742 µs (717297 copy, 445 gen) Median 260213 µs (259890 copy, 322 gen) CPU:

(38)

Test nr 17/20 Size: 7200 Tiles GPU:

Min 242895 µs (242611 copy, 284 gen) Max 680470 µs (680206 copy, 264 gen) Median 269772 µs (269483 copy, 289 gen) CPU:

Min 14909036 µs, Max 16086910 µs, Median 15605373 µs

Test nr 18/20 Size: 7600 Tiles GPU:

Min 455229 µs (454899 copy, 329 gen) Max 731049 µs (730707 copy, 340 gen) Median 482874 µs (482414 copy, 458 gen) CPU:

Min 15864196 µs, Max 16876062 µs, Median 16355299 µs

Test nr 19/20 Size: 8000 Tiles GPU:

Min 278020 µs (277648 copy, 372 gen) Max 821476 µs (821184 copy, 291 gen) Median 486160 µs (485707 copy, 453 gen) CPU:

Min 16809015 µs, Max 17263094 µs, Median 17072544 µs

Test nr 20/20 Size: 8400 Tiles GPU:

Min 294350 µs (294045 copy, 304 gen) Max 843773 µs (843463 copy, 309 gen) Median 507940 µs (507489 copy, 450 gen) CPU:

References

Related documents

Även fast det går att urskilja datorer med olika hårdvaror visar diagrammen i Figur 14 till Figur 19, tabellerna i Tabell 12 till Tabell 17 och resultatet

Syftet med studien var att undersöka hur svenska elitidrottare upplevde att deras motivation förändrades under våren 2020 med Covid-19 pandemin när alla tävlingar blev inställda och

Then we can allocate memory object that the kernel function can work with. We create new cl_mem objects and use the function clCreateBuffer to set the memory arena and

This is likely due to the extra complexity of the hybrid execution implementation of the Scan skeleton, where the performance of the CPU and the accelerator partitions do

The experiments could be preceded with a small task based experiment in which the user is provided with a gesture based system or other similar VKs as well as small typing

The aim of this population-based co- hort study was to estimate the incidence of LLA (at or proximal to the transmeta- tarsal level) performed for peripheral vas- cular disease

Bilderna av den tryckta texten har tolkats maskinellt (OCR-tolkats) för att skapa en sökbar text som ligger osynlig bakom bilden.. Den maskinellt tolkade texten kan

Jag färgar mina varpflätor och inslagsgarn innan jag sätter upp väven för att få fram färg som jag vill arbeta med genom hela varpen och med inslag?. Men också för att få en