|
| 1 | +{-# LANGUAGE OverloadedLists #-} |
| 2 | +{-# LANGUAGE OverloadedStrings #-} |
| 3 | +{-# LANGUAGE RecordWildCards #-} |
| 4 | + |
| 5 | +-- | Let's draw a triangle! |
| 6 | +module Main (main) where |
| 7 | + |
| 8 | +import Control.Monad (unless) |
| 9 | +import Control.Monad.Trans.Class (lift) |
| 10 | +import Control.Monad.Trans.Except (runExceptT) |
| 11 | +import Control.Monad.Trans.Maybe (MaybeT (MaybeT), maybeToExceptT) |
| 12 | +import Data.Default (def) |
| 13 | +import Data.Text (Text) |
| 14 | +import qualified Data.Text as Text |
| 15 | +import qualified Data.Text.IO as TextIO |
| 16 | +import qualified Graphics.UI.GLFW as GLFW |
| 17 | +import System.Exit (exitFailure) |
| 18 | +import qualified WGPU |
| 19 | + |
| 20 | +main :: IO () |
| 21 | +main = do |
| 22 | + TextIO.putStrLn "Triangle Example" |
| 23 | + |
| 24 | + -- start GLFW |
| 25 | + initResult <- GLFW.init |
| 26 | + unless initResult $ do |
| 27 | + TextIO.putStrLn "Failed to initialize GLFW" |
| 28 | + exitFailure |
| 29 | + |
| 30 | + -- create the GLFW window without a "client API" |
| 31 | + let initWidth, initHeight :: Int |
| 32 | + initWidth = 640 |
| 33 | + initHeight = 480 |
| 34 | + GLFW.windowHint (GLFW.WindowHint'ClientAPI GLFW.ClientAPI'NoAPI) |
| 35 | + window <- do |
| 36 | + mWin <- GLFW.createWindow initWidth initHeight "Triangle" Nothing Nothing |
| 37 | + case mWin of |
| 38 | + Just w -> pure w |
| 39 | + Nothing -> do |
| 40 | + TextIO.putStrLn "Failed to create GLFW window" |
| 41 | + exitFailure |
| 42 | + |
| 43 | + WGPU.withInstance "libwgpu_native.dylib" (Just WGPU.logStdout) $ \inst -> do |
| 44 | + -- set the logging level |
| 45 | + WGPU.setLogLevel inst WGPU.Warn |
| 46 | + |
| 47 | + -- print the version of the WGPU library |
| 48 | + version <- WGPU.getVersion inst |
| 49 | + TextIO.putStrLn $ "WGPU version: " <> WGPU.versionToText version |
| 50 | + |
| 51 | + -- fetch resources (surface, adpater, device) |
| 52 | + Resources {..} <- getResources inst window >>= getOrFail |
| 53 | + |
| 54 | + shader <- WGPU.createShaderModuleWGSL device "shader" shaderSrc |
| 55 | + swapChainFormat <- WGPU.getSwapChainPreferredFormat surface adapter |
| 56 | + swapChain <- |
| 57 | + WGPU.createSwapChain |
| 58 | + device |
| 59 | + surface |
| 60 | + WGPU.SwapChainDescriptor |
| 61 | + { swapChainLabel = "SwapChain", |
| 62 | + usage = WGPU.TextureUsageRenderAttachment, |
| 63 | + swapChainFormat = swapChainFormat, |
| 64 | + width = fromIntegral initWidth, |
| 65 | + height = fromIntegral initHeight, |
| 66 | + presentMode = WGPU.PresentModeFifo |
| 67 | + } |
| 68 | + pipelineLayout <- |
| 69 | + WGPU.createPipelineLayout |
| 70 | + device |
| 71 | + (WGPU.PipelineLayoutDescriptor "Pipeline" []) |
| 72 | + pipeline <- |
| 73 | + WGPU.createRenderPipeline |
| 74 | + device |
| 75 | + WGPU.RenderPipelineDescriptor |
| 76 | + { renderPipelineLabel = "Render Pipeline", |
| 77 | + layout = WGPU.SJust pipelineLayout, |
| 78 | + vertex = WGPU.VertexState shader "vs_main" [], |
| 79 | + primitive = def, |
| 80 | + depthStencil = WGPU.SNothing, |
| 81 | + multisample = WGPU.MultisampleState 1 0xFFFFFFFF False, |
| 82 | + fragment = |
| 83 | + WGPU.SJust $ |
| 84 | + WGPU.FragmentState |
| 85 | + shader |
| 86 | + "fs_main" |
| 87 | + [ WGPU.ColorTargetState |
| 88 | + swapChainFormat |
| 89 | + (WGPU.SJust (WGPU.BlendState def def)) |
| 90 | + WGPU.colorWriteMaskAll |
| 91 | + ] |
| 92 | + } |
| 93 | + |
| 94 | + let loop = do |
| 95 | + -- render |
| 96 | + nextTexture <- WGPU.getSwapChainCurrentTextureView swapChain |
| 97 | + encoder <- WGPU.createCommandEncoder device "Command Encoder" |
| 98 | + renderPass <- |
| 99 | + WGPU.beginRenderPass |
| 100 | + encoder |
| 101 | + ( WGPU.RenderPassDescriptor |
| 102 | + { renderPassLabel = "Render Pass", |
| 103 | + colorAttachments = |
| 104 | + [ WGPU.RenderPassColorAttachment |
| 105 | + nextTexture |
| 106 | + WGPU.SNothing |
| 107 | + ( WGPU.Operations |
| 108 | + (WGPU.LoadOpClear (WGPU.Color 0 0 0 1)) |
| 109 | + WGPU.StoreOpStore |
| 110 | + ) |
| 111 | + ], |
| 112 | + depthStencilAttachment = WGPU.SNothing |
| 113 | + } |
| 114 | + ) |
| 115 | + WGPU.renderPassSetPipeline renderPass pipeline |
| 116 | + WGPU.renderPassDraw renderPass (WGPU.Range 0 3) (WGPU.Range 0 1) |
| 117 | + WGPU.endRenderPass renderPass |
| 118 | + commandBuffer <- WGPU.commandEncoderFinish encoder "Command Buffer" |
| 119 | + WGPU.queueSubmit queue [commandBuffer] |
| 120 | + WGPU.swapChainPresent swapChain |
| 121 | + |
| 122 | + -- handle GLFW quit event |
| 123 | + GLFW.pollEvents |
| 124 | + shouldClose <- GLFW.windowShouldClose window |
| 125 | + unless shouldClose loop |
| 126 | + |
| 127 | + loop |
| 128 | + |
| 129 | + -- close down GLFW |
| 130 | + GLFW.destroyWindow window |
| 131 | + GLFW.terminate |
| 132 | + |
| 133 | +newtype Error = Error Text deriving (Eq, Show) |
| 134 | + |
| 135 | +getOrFail :: Either Error a -> IO a |
| 136 | +getOrFail ma = |
| 137 | + case ma of |
| 138 | + Right x -> pure x |
| 139 | + Left err -> failWith err |
| 140 | + |
| 141 | +failWith :: Error -> IO a |
| 142 | +failWith (Error err) = do |
| 143 | + TextIO.putStrLn err |
| 144 | + exitFailure |
| 145 | + |
| 146 | +data Resources = Resources |
| 147 | + { surface :: WGPU.Surface, |
| 148 | + adapter :: WGPU.Adapter, |
| 149 | + device :: WGPU.Device, |
| 150 | + queue :: WGPU.Queue |
| 151 | + } |
| 152 | + |
| 153 | +getResources :: WGPU.Instance -> GLFW.Window -> IO (Either Error Resources) |
| 154 | +getResources inst window = runExceptT $ do |
| 155 | + -- fetch a surface for the window |
| 156 | + surface <- lift $ WGPU.createGLFWSurface inst window |
| 157 | + -- fetch an adapter for the surface |
| 158 | + adapter <- |
| 159 | + maybeToExceptT |
| 160 | + (Error "Failed to obtain WGPU Adapter") |
| 161 | + (MaybeT $ WGPU.requestAdapter surface) |
| 162 | + -- fetch a device for the adapter |
| 163 | + let deviceDescriptor :: WGPU.DeviceDescriptor |
| 164 | + deviceDescriptor = def {WGPU.limits = def {WGPU.maxBindGroups = 1}} |
| 165 | + device <- |
| 166 | + maybeToExceptT |
| 167 | + (Error "Failed to obtain WGPU Device") |
| 168 | + (MaybeT $ WGPU.requestDevice adapter deviceDescriptor) |
| 169 | + |
| 170 | + queue <- lift $ WGPU.getQueue device |
| 171 | + |
| 172 | + pure Resources {..} |
| 173 | + |
| 174 | +shaderSrc :: WGPU.WGSL |
| 175 | +shaderSrc = |
| 176 | + WGPU.WGSL $ |
| 177 | + Text.intercalate |
| 178 | + "\n" |
| 179 | + [ "[[stage(vertex)]]", |
| 180 | + "fn vs_main([[builtin(vertex_index)]] in_vertex_index: u32) -> [[builtin(position)]] vec4<f32> {", |
| 181 | + " let x = f32(i32(in_vertex_index) - 1);", |
| 182 | + " let y = f32(i32(in_vertex_index & 1u) * 2 - 1);", |
| 183 | + " return vec4<f32>(x, y, 0.0, 1.0);", |
| 184 | + "}", |
| 185 | + "", |
| 186 | + "[[stage(fragment)]]", |
| 187 | + "fn fs_main([[builtin(position)]] in: vec4<f32>) -> [[location(0)]] vec4<f32> {", |
| 188 | + " return vec4<f32>(in.x/640.0, in.y/480.0, 1.0, 1.0);", |
| 189 | + "}" |
| 190 | + ] |
0 commit comments