packages feed

keid-render-basic (empty) → 0.1.0.0

raw patch · 34 files changed

+4644/−0 lines, 34 filesdep +GLFW-bdep +VulkanMemoryAllocatordep +aesonsetup-changed

Dependencies added: GLFW-b, VulkanMemoryAllocator, aeson, base, binary, bytestring, cryptohash-md5, derive-storable, derive-storable-plugin, foldl, geomancy, keid-core, neat-interpolation, resourcet, rio, rio-app, tagged, text, unliftio, vector, vulkan, vulkan-utils, zstd

Files

+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for keid-render-basic++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright IC Rainbow (c) 2021++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Author name here nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,15 @@+# Keid Engine - Basic forward rendering++- Forward renderpass with MSAA+  * Common descriptor set+  * Unlit pipelines+    * Colored+    * Textured+  * Lit pipelines+    * Colored+    * Textured+    * Material+  * Skybox pipeline+  * Debug pipeline+- Shadowmap pass+  * Shadow-casting pipeline
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ keid-render-basic.cabal view
@@ -0,0 +1,142 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.34.4.+--+-- see: https://github.com/sol/hpack++name:           keid-render-basic+version:        0.1.0.0+synopsis:       Basic rendering programs for Keid engine.+category:       Game Engine+author:         IC Rainbow+maintainer:     keid@aenor.ru+copyright:      2021 IC Rainbow+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md+    ChangeLog.md++source-repository head+  type: git+  location: https://gitlab.com/keid/engine++library+  exposed-modules:+      Engine.UI.Message+      Render.Basic+      Render.Code.Lit+      Render.Debug.Model+      Render.Debug.Pipeline+      Render.DescSets.Set0+      Render.DescSets.Set0.Code+      Render.DescSets.Sun+      Render.Font.EvanwSdf.Model+      Render.Font.EvanwSdf.Pipeline+      Render.ForwardMsaa+      Render.Lit.Colored.Model+      Render.Lit.Colored.Pipeline+      Render.Lit.Material+      Render.Lit.Material.Collect+      Render.Lit.Material.Model+      Render.Lit.Material.Pipeline+      Render.Lit.Textured.Model+      Render.Lit.Textured.Pipeline+      Render.ShadowMap.Pipeline+      Render.ShadowMap.RenderPass+      Render.Skybox.Pipeline+      Render.Unlit.Colored.Model+      Render.Unlit.Colored.Pipeline+      Render.Unlit.Textured.Model+      Render.Unlit.Textured.Pipeline+      Resource.Font+      Resource.Font.EvanW+      Resource.Mesh.Lit+  other-modules:+      Paths_keid_render_basic+  hs-source-dirs:+      src+  default-extensions:+      NoImplicitPrelude+      ApplicativeDo+      BangPatterns+      BinaryLiterals+      BlockArguments+      ConstrainedClassMethods+      ConstraintKinds+      DataKinds+      DefaultSignatures+      DeriveDataTypeable+      DeriveFunctor+      DeriveGeneric+      DeriveLift+      DeriveTraversable+      DerivingStrategies+      DerivingVia+      DuplicateRecordFields+      EmptyCase+      EmptyDataDeriving+      ExistentialQuantification+      ExplicitForAll+      FlexibleContexts+      FlexibleInstances+      FunctionalDependencies+      GADTs+      GeneralizedNewtypeDeriving+      HexFloatLiterals+      ImportQualifiedPost+      InstanceSigs+      KindSignatures+      LambdaCase+      LiberalTypeSynonyms+      MultiParamTypeClasses+      NamedFieldPuns+      NamedWildCards+      NumDecimals+      NumericUnderscores+      OverloadedStrings+      PatternSynonyms+      PostfixOperators+      QuantifiedConstraints+      QuasiQuotes+      RankNTypes+      RecordWildCards+      ScopedTypeVariables+      StandaloneDeriving+      StandaloneKindSignatures+      StrictData+      TemplateHaskell+      TupleSections+      TypeApplications+      TypeFamilies+      TypeOperators+      TypeSynonymInstances+      UnicodeSyntax+      ViewPatterns+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints+  build-depends:+      GLFW-b+    , VulkanMemoryAllocator+    , aeson+    , base >=4.7 && <5+    , binary+    , bytestring+    , cryptohash-md5+    , derive-storable+    , derive-storable-plugin+    , foldl+    , geomancy+    , keid-core+    , neat-interpolation+    , resourcet+    , rio >=0.1.12.0+    , rio-app+    , tagged+    , text+    , unliftio+    , vector+    , vulkan+    , vulkan-utils+    , zstd+  default-language: Haskell2010
+ src/Engine/UI/Message.hs view
@@ -0,0 +1,129 @@+module Engine.UI.Message+  ( Process+  , Input(..)+  , spawn+  , spawnFromR+  , mkAttrs++  , Observer+  , Buffer+  , newObserver+  , observe+  ) where++import RIO++import Control.Monad.Trans.Resource (ResourceT)+import Control.Monad.Trans.Resource qualified as Resource+import Data.Text qualified as Text+import Data.Vector.Storable qualified as Storable+import Geomancy (Vec4)+import Geomancy.Vec4 qualified as Vec4+import Vulkan.Core10 qualified as Vk++import Engine.Types qualified as Engine+import Engine.UI.Layout qualified as Layout+import Engine.Vulkan.Types (HasVulkan)+import Engine.Worker qualified as Worker+import Render.Font.EvanwSdf.Model qualified as EvanwSdf+import Render.Samplers qualified as Samplers+import Resource.Buffer qualified as Buffer+import Resource.Font.EvanW qualified as Font++type Process = Worker.Merge (Storable.Vector EvanwSdf.InstanceAttrs)++spawn+  :: ( Worker.HasOutput box+     , Worker.GetOutput box ~ Layout.Box+     , Worker.HasOutput input+     , Worker.GetOutput input ~ Input+     ) => box -> input -> RIO env Process+spawn = Worker.spawnMerge2 mkAttrs++spawnFromR+  :: ( Resource.MonadResource (RIO env)+     , Worker.HasOutput box+     , Worker.GetOutput box ~ Layout.Box+     , Worker.HasOutput source+     )+  => box+  -> source+  -> (Worker.GetOutput source -> Input)+  -> RIO env (Resource.ReleaseKey, Resource.ReleaseKey, Process)+spawnFromR parent inputProc mkMessage = do+  inputP <- Worker.spawnMerge1 mkMessage inputProc+  messageP <- spawn parent inputP++  (,,messageP)+    <$> Worker.register inputP+    <*> Worker.register messageP++data Input = Input+  { inputText         :: Text+  , inputFontId       :: Int32+  , inputFont         :: Font.Container+  , inputOrigin       :: Layout.Alignment+  , inputSize         :: Float+  , inputColor        :: Vec4+  , inputOutline      :: Vec4+  , inputOutlineWidth :: Float+  , inputSmoothing    :: Float+  }++mkAttrs :: Layout.Box -> Input -> Storable.Vector EvanwSdf.InstanceAttrs+mkAttrs Layout.Box{..} Input{..} =+  if inputSize < 1 then mempty else Storable.fromList do+    Font.PutChar{..} <- chars+    pure EvanwSdf.InstanceAttrs+      { vertRect     = Vec4.fromVec22 pcPos pcSize+      , fragRect     = Vec4.fromVec22 pcOffset pcScale+      , color        = inputColor+      , outlineColor = inputOutline -- vec4 0 0.25 0 0.25+      , textureId    = inputFontId+      , samplerId    = samplerId+      , smoothing    = inputSmoothing -- 1/16+      , outlineWidth = inputOutlineWidth -- 3/16+      }+  where+    (scale, chars) = Font.putLine+      boxSize+      boxPosition+      inputOrigin+      inputSize+      inputFont+      (Text.unpack inputText)++    samplerId =+      if scale > 1 then+        Samplers.linear Samplers.indices+      else+        Samplers.linear Samplers.indices++type Buffer = Buffer.Allocated 'Buffer.Coherent EvanwSdf.InstanceAttrs++type Observer = Worker.ObserverIO Buffer++newObserver :: Int -> ResourceT (Engine.StageRIO st) Observer+newObserver initialSize = do+  context <- ask++  messageData <- Buffer.createCoherent context Vk.BUFFER_USAGE_VERTEX_BUFFER_BIT initialSize mempty+  observer <- Worker.newObserverIO messageData++  void $! Resource.register do+    vData <- readIORef observer+    Buffer.destroyAll context vData++  pure observer++observe+  :: ( HasVulkan env+     , Worker.HasOutput source+     , Worker.GetOutput source ~ Storable.Vector EvanwSdf.InstanceAttrs+     )+  => source+  -> Observer -> RIO env ()+observe messageP observer = do+  context <- ask+  Worker.observeIO_ messageP observer $+    Buffer.updateCoherentResize_ context
+ src/Render/Basic.hs view
@@ -0,0 +1,151 @@+{- |+  All the provided render passes and pipelines packaged and delivered.+-}++module Render.Basic where++import RIO++import Control.Monad.Trans.Resource (ResourceT)+import Data.Tagged (Tagged(..))+import RIO.Vector.Partial as Vector (headM)+import Vulkan.Core10 qualified as Vk++-- keid-core++import Engine.Types qualified as Engine+import Engine.Types (StageRIO)+import Engine.Vulkan.Pipeline qualified as Pipeline+import Engine.Vulkan.Types (DsBindings, HasSwapchain(getMultisample), RenderPass(..))++-- keid-render-basic++import Render.Debug.Pipeline qualified as Debug+import Render.DescSets.Set0 (Scene)+import Render.DescSets.Set0 qualified as Scene+import Render.DescSets.Sun (Sun)+import Render.DescSets.Sun qualified as Sun+import Render.Font.EvanwSdf.Pipeline qualified as EvanwSdf+import Render.ForwardMsaa (ForwardMsaa)+import Render.ForwardMsaa qualified as ForwardMsaa+import Render.Lit.Colored.Pipeline qualified as LitColored+import Render.Lit.Material.Pipeline qualified as LitMaterial+import Render.Lit.Textured.Pipeline qualified as LitTextured+import Render.Samplers qualified as Samplers+import Render.ShadowMap.Pipeline qualified as ShadowPipe+import Render.ShadowMap.RenderPass (ShadowMap)+import Render.ShadowMap.RenderPass qualified as ShadowPass+import Render.Skybox.Pipeline qualified as Skybox+import Render.Unlit.Colored.Pipeline qualified as UnlitColored+import Render.Unlit.Textured.Pipeline qualified as UnlitTextured++type Stage = Engine.Stage RenderPasses Pipelines+type Frame = Engine.Frame RenderPasses Pipelines+type StageFrameRIO r s a = Engine.StageFrameRIO RenderPasses Pipelines r s a++data RenderPasses = RenderPasses+  { rpForwardMsaa :: ForwardMsaa+  , rpShadowPass  :: ShadowMap+  }++instance RenderPass RenderPasses where+  allocateRenderpass_ context = do+    rpForwardMsaa <- ForwardMsaa.allocateMsaa context++    -- TODO: get from config+    let+      size      = 4096 -- XXX: 2048 is kinda minimum, even with PCF enabled+      numLayers = 2    -- XXX: no-cascade sun + moon+    rpShadowPass <- ShadowPass.allocate context size numLayers++    pure RenderPasses{..}++  updateRenderpass context RenderPasses{..} = RenderPasses+    <$> ForwardMsaa.updateMsaa context rpForwardMsaa+    <*> pure rpShadowPass -- XXX: not a screen pass++  refcountRenderpass RenderPasses{..} = do+    refcountRenderpass rpForwardMsaa+    refcountRenderpass rpShadowPass++data Pipelines = Pipelines+  { pEvanwSdf :: EvanwSdf.Pipeline+  , pSkybox   :: Skybox.Pipeline+  , pDebug    :: Debug.Pipeline++  , pLitColored       :: LitColored.Pipeline+  , pLitColoredBlend  :: LitColored.Pipeline+  , pLitMaterial      :: LitMaterial.Pipeline+  , pLitMaterialBlend :: LitMaterial.Pipeline+  , pLitTextured      :: LitTextured.Pipeline+  , pLitTexturedBlend :: LitTextured.Pipeline++  , pUnlitColored        :: UnlitColored.Pipeline+  , pUnlitColoredNoDepth :: UnlitColored.Pipeline+  , pUnlitTextured       :: UnlitTextured.Pipeline+  , pUnlitTexturedBlend  :: UnlitTextured.Pipeline+  , pWireframe           :: UnlitColored.Pipeline+  , pWireframeNoDepth    :: UnlitColored.Pipeline++  , pShadowCast :: ShadowPipe.Pipeline+  }++allocatePipelines_+  :: HasSwapchain swapchain+  => swapchain+  -> RenderPasses+  -> ResourceT (StageRIO st) Pipelines+allocatePipelines_ swapchain renderpasses = do+  (_, samplers) <- Samplers.allocate swapchain+  allocatePipelines+    (Scene.set0 samplers (Left 3) (Left 0) 2)+    swapchain+    renderpasses++allocatePipelines+  :: HasSwapchain swapchain+  => Tagged Scene DsBindings+  -> swapchain+  -> RenderPasses+  -> ResourceT (StageRIO st) Pipelines+allocatePipelines sceneBinds swapchain RenderPasses{..} = do+  let msaa = getMultisample swapchain++  pDebug    <- Debug.allocate msaa sceneBinds rpForwardMsaa+  pEvanwSdf <- EvanwSdf.allocate msaa sceneBinds rpForwardMsaa+  pSkybox   <- Skybox.allocate msaa sceneBinds rpForwardMsaa++  pLitColored       <- LitColored.allocate msaa sceneBinds rpForwardMsaa+  pLitColoredBlend  <- LitColored.allocateBlend msaa sceneBinds rpForwardMsaa+  pLitMaterial      <- LitMaterial.allocate msaa sceneBinds rpForwardMsaa+  pLitMaterialBlend <- LitMaterial.allocateBlend msaa sceneBinds rpForwardMsaa+  pLitTextured      <- LitTextured.allocate msaa sceneBinds rpForwardMsaa+  pLitTexturedBlend <- LitTextured.allocateBlend msaa sceneBinds rpForwardMsaa++  pUnlitColored        <- UnlitColored.allocate True msaa sceneBinds rpForwardMsaa+  pUnlitColoredNoDepth <- UnlitColored.allocate False msaa sceneBinds rpForwardMsaa+  pUnlitTextured       <- UnlitTextured.allocate msaa sceneBinds rpForwardMsaa+  pUnlitTexturedBlend  <- UnlitTextured.allocateBlend msaa sceneBinds rpForwardMsaa+  pWireframe           <- UnlitColored.allocateWireframe True msaa sceneBinds rpForwardMsaa+  pWireframeNoDepth    <- UnlitColored.allocateWireframe False msaa sceneBinds rpForwardMsaa++  let sunBinds = Sun.set0+  pShadowCast <- ShadowPipe.allocate sunBinds rpShadowPass ShadowPipe.defaults++  pure Pipelines{..}++getSceneLayout :: Pipelines -> Tagged '[Scene] Vk.DescriptorSetLayout+getSceneLayout Pipelines{pLitColored} =+  case Vector.headM (unTagged $ Pipeline.pDescLayouts pLitColored) of+    Nothing ->+      error "pLitColored has at least set0 in layout"+    Just set0layout ->+      Tagged set0layout++getSunLayout :: Pipelines -> Tagged '[Sun] Vk.DescriptorSetLayout+getSunLayout Pipelines{pShadowCast} =+  case Vector.headM (unTagged $ Pipeline.pDescLayouts pShadowCast) of+    Nothing ->+      error "pShadowCast has at least set0 in layout"+    Just set0layout ->+      Tagged set0layout
+ src/Render/Code/Lit.hs view
@@ -0,0 +1,322 @@+module Render.Code.Lit+  ( raySphereIntersection+  , hgPhase+  , structLight+  , structMaterial+  , shadowFuns+  , litMain+  , brdfSpecular+  ) where++import Render.Code (Code(..), trimming)++raySphereIntersection :: Code+raySphereIntersection = Code+  [trimming|+    vec2 raySphereIntersection(vec3 rayOrigin, vec3 rayDir, vec3 sphereCenter, float sphereRadius) {+      vec3 tmp = rayOrigin - sphereCenter;++      float b = dot(rayDir, tmp);+      float c = dot(tmp, tmp) - sphereRadius * sphereRadius;++      float disc = b * b - c;++      if(disc < 0.0) return vec2(-M_MAX, -M_MAX);++      float disc_sqrt = sqrt(disc);++      float t0 = -b - disc_sqrt;+      float t1 = -b + disc_sqrt;++      return vec2(t0, t1);+    }+  |]++-- | The Henyey-Greenstein Phase Function+hgPhase :: Code+hgPhase = Code+  [trimming|+    float hgPhase(float nu, float g) {+      float g2 = g * g;+      return+        (+          3.0 *+          (1.0 - g2) *+          (1.0 + nu * nu)+        ) /+        (+          2.0 *+          (2.0 + g2) *+          pow(+            1.0 + g2 - 2.0 * g * nu,+            1.5+          )+        );+    }+  |]++structLight :: Code+structLight = Code+  [trimming|+    struct Light {+      mat4 viewProjection; // bring model positions into light-space+      vec4 shadow;         // offset-x, offset-y, shadowmap index, size+      vec4 position;       // alpha: unused+      vec4 direction;      // alpha: unused+      vec4 color;          // alpha: energy+      // vec2 cutoff;      // inner / outer+    };+  |]++structMaterial :: Code+structMaterial = Code+  [trimming|+    struct Material {+      vec4 baseColor;+      vec2 metallicRoughness;+      vec4 emissive;+      float normalScale;+      float alphaCutoff;++      int baseColorTex;+      int metallicRoughnessTex;+      int emissiveTex;+      int normalTex;+      int ambientOcclusionTex;+    };+  |]++shadowFuns :: Code+shadowFuns = Code+  [trimming|+    float shadow_factor(vec3 shadowCoord, float mapIx, vec2 offset) {+      if (abs(shadowCoord.x) > 1.0 ||+          abs(shadowCoord.y) > 1.0 ||+          abs(shadowCoord.z) > 1.0)+            return 0.0; // XXX: 1.0 would be better for directional++      vec4 uvwi = vec4(shadowCoord.xy * 0.5 + 0.5 + offset, mapIx, shadowCoord.z);+      return texture(shadowmaps, uvwi);+    }++    float filterPCF(vec3 shadowCoord, float mapIx) {+      float shadowFactor = 0.0;+      int count = 0;+      int range = 1;++      for (int x = -range; x <= range; x++) {+        for (int y = -range; y <= range; y++) {+          shadowFactor += shadow_factor(+            shadowCoord,+            mapIx,+            vec2(x, y) * PCF_STEP+          );+          count++;+        }+      }+      return shadowFactor / count;+    }+  |]++litMain :: Code+litMain = Code+  [trimming|+    vec3 albedo =+      // XXX: not needed, we're in linear already+      // pow(baseColor.rgb, vec3(2.2));+      baseColor.rgb;+    vec3 F0 = mix(vec3(0.04), albedo, metallic);++    vec3 ray = scene.viewPosition.xyz - fPosition.xyz;+    vec3 rayDir = normalize(ray); // V+    float quadrance = dot(ray, ray);+    float distance = sqrt(quadrance);++    // XXX: provided by caller+    // vec3 normal = normalize(fNormal); // N++    vec3 Lo = vec3(0.0);+    for (int l = 0; l < scene.numLights; l++) {+      // XXX: directional lights' hit angle doesn't depend on fragment position+      vec3 lightDir = normalize(lights[l].direction.xyz); // L++      float shade = 1.0; // XXX: 0 - occluded, 1 - lit+      if (lights[l].shadow.w > 0) {+        vec4 light_space_pos = lights[l].viewProjection * fPosition;+        vec4 shadowCoord = light_space_pos /= light_space_pos.w;++        // TODO: pick on specialization constant+        shade = filterPCF(shadowCoord.xyz, lights[l].shadow.z);+        // shade = shadow_factor(shadowCoord.xyz, lights[l].shadow.z, vec2(0));+      }++      Lo += brdfSpecular(+        lightDir,+        ray,+        normal,+        F0,+        metallic,+        roughness,+        albedo,+        lights[l].color.rgb * lights[l].color.a+      ) * shade;+    }++    vec3 reflection = prefilteredReflection(reflect(rayDir, normal), roughness).rgb;++    // IBL+    vec3 irradiance = vec3(0);+    if (scene.envCubeId > -1) {+      irradiance = textureLod(+        samplerCube(+          cubes[nonuniformEXT(scene.envCubeId)],+          samplers[0]+        ),+        -normal,+        IRRADIANCE_LOD+      ).rgb;+    }++    // Specular reflectance+    vec2 ibl = texture(+      sampler2D(+        textures[BRDF_LUT],+        samplers[BRDF_LUT_SAMPLER]+      ),+      vec2(roughness, max(dot(normal, rayDir), 0.0))+    ).rg;+    vec3 F = F_SchlickR(max(dot(normal, rayDir), 0.0), F0, roughness);+    vec3 specular = occlusion * reflection * (F * ibl.x + ibl.y);++    vec3 kD = (1.0 - F) * (1.0 - metallic);++    // Diffuse based on irradiance+    vec3 diffuseI = occlusion * irradiance * albedo;+    vec3 ambient = kD * diffuseI + specular;++    // Combine with ambient+    vec3 color = Lo + ambient;++    // Tone mapping+    color =+      Uncharted2Tonemap(color * 4.5) /+      Uncharted2Tonemap(vec3(11.2)); // White point++    // Gamma correction+    // XXX: not needed, we're in linear already+    // color = pow(color, vec3(1.0/2.2));++    // Happily ever after+    oColor = vec4(color, baseColor.a);+  |]++brdfSpecular :: Code+brdfSpecular = Code+  [trimming|+    // TODO: unhardcode+    const int BRDF_LUT = 2;+    const int BRDF_LUT_SAMPLER = 3; // linear/mip0/no-repeat+    const float MAX_REFLECTION_LOD = 9.0; // todo: param/const+    const float IRRADIANCE_LOD = 10.0; // todo: param/const++    // Normal Distribution function --------------------------------------+    float D_GGX(float dotNH, float roughness) {+      float alpha = roughness * roughness;+      float alpha2 = alpha * alpha;+      float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0;+      return (alpha2) / (3.14159265359 * denom*denom);+    }++    // Geometric Shadowing function --------------------------------------+    float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness) {+      float r = (roughness + 1.0);+      float k = (r*r) / 8.0;+      float GL = dotNL / (dotNL * (1.0 - k) + k);+      float GV = dotNV / (dotNV * (1.0 - k) + k);+      return GL * GV;+    }++    // Fresnel function ----------------------------------------------------+    vec3 F_Schlick(float cosTheta, vec3 F0) {+      return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);+    }++    vec3 F_SchlickR(float cosTheta, vec3 F0, float roughness) {+      return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);+    }++    vec3 prefilteredReflection(vec3 R, float roughness) {+      vec3 color = vec3(0);++      if (scene.envCubeId > -1) {+        float lod = roughness * MAX_REFLECTION_LOD;+        float lodf = floor(lod);+        float lodc = ceil(lod);++        vec3 a = textureLod(+          samplerCube(+            cubes[nonuniformEXT(scene.envCubeId)],+            samplers[0]+          ),+          R,+          lodf+        ).rgb;++        vec3 b = textureLod(+          samplerCube(+            cubes[nonuniformEXT(scene.envCubeId)],+            samplers[0]+          ),+          R,+          lodc+        ).rgb;++        return mix(a, b, lod - lodf);+++//        color = texture(+//          samplerCube(+//            cubes[nonuniformEXT(scene.envCubeId)],+//            samplers[2] // XXX: linear/mip0/repeat+//          ),+//          fragUVW,+//          10+//        );+      }+      return color;+    }++    vec3 brdfSpecular(vec3 L, vec3 V, vec3 N, vec3 F0, float metallic, float roughness, vec3 ALBEDO, vec3 lightColor) {+      // Precalculate vectors and dot products+      vec3 H = normalize (V + L);+      float dotNH = clamp(dot(N, H), 0.0, 1.0);+      float dotNV = clamp(dot(N, V), 0.0, 1.0);+      float dotNL = clamp(dot(N, L), 0.0, 1.0);++      vec3 color = vec3(0.0);++      if (dotNL > 0.0) {+        // D = Normal distribution (Distribution of the microfacets)+        float D = D_GGX(dotNH, roughness);+        // G = Geometric shadowing term (Microfacets shadowing)+        float G = G_SchlicksmithGGX(dotNL, dotNV, roughness);+        // F = Fresnel factor (Reflectance depending on angle of incidence)+        vec3 F = F_Schlick(dotNV, F0);+        vec3 spec = D * F * G / (4.0 * dotNL * dotNV + 0.001);+        vec3 kD = (vec3(1.0) - F) * (1.0 - metallic);+        color += (kD * ALBEDO / 3.1415926535897932384626433832795 + spec) * dotNL * lightColor;+      }++      return color;+    }++    vec3 Uncharted2Tonemap(vec3 color) {+      float A = 0.15;+      float B = 0.50;+      float C = 0.10;+      float D = 0.20;+      float E = 0.02;+      float F = 0.30;+      return ((color * (A * color + C * B) + D * E) / (color * (A * color + B) + D * F)) - E / F;+    }+  |]
+ src/Render/Debug/Model.hs view
@@ -0,0 +1,218 @@+module Render.Debug.Model+  ( Model+  , VertexAttrs+  , vkVertexAttrs++  , InstanceAttrs(..)+  , instanceAttrs++  , StorableAttrs+  , storableAttrs1++  , InstanceBuffers(..)++  , TextureParams(..)+  , vkInstanceTexture++    -- TODO: extract and merge with UnlitTextured+  , allocateInstancesWith+  , allocateInstancesCoherent+  , allocateInstancesCoherent_+  , updateCoherentResize_+  , Transform+  ) where++import RIO+++import Foreign (Storable(..))+import Geomancy (Transform, Vec2, Vec4)+import Geomancy.Vec3 qualified as Vec3+import RIO.Vector.Storable qualified as Storable+import UnliftIO.Resource (MonadResource, ReleaseKey, ResourceT, allocate)+import Vulkan.Core10 qualified as Vk+import Vulkan.NamedType ((:::))+import Vulkan.Zero (Zero(..))++import Engine.Vulkan.Types (HasVulkan)+import Resource.Buffer qualified as Buffer+import Resource.Model qualified as Model++type Model buf = Model.Indexed buf Vec3.Packed VertexAttrs++type VertexAttrs = "uv" ::: Vec2++vkVertexAttrs :: [Vk.Format]+vkVertexAttrs =+  [ Vk.FORMAT_R32G32_SFLOAT -- vTexCoord :: vec2+  ]++-- | Data for a single element.+data InstanceAttrs = InstanceAttrs+  { textureParams :: TextureParams+  , transformMat4 :: Transform+  }++instance Zero InstanceAttrs where+  zero = InstanceAttrs+    { textureParams = zero+    , transformMat4 = mempty+    }++instanceAttrs :: Int32 -> Int32 -> [Transform] -> InstanceAttrs+instanceAttrs samplerId textureId transforms = InstanceAttrs+  { textureParams = zero+      { tpSamplerId = samplerId+      , tpTextureId = textureId+      }+  , transformMat4 = mconcat transforms+  }++-- | Intermediate data to be shipped.+type StorableAttrs =+  ( Storable.Vector TextureParams+  , Storable.Vector Transform+  )++storableAttrs1 :: Int32 -> Int32 -> [Transform] -> StorableAttrs+storableAttrs1 samplerId textureId transforms =+  ( Storable.singleton textureParams+  , Storable.singleton transformMat4+  )+  where+    InstanceAttrs{..} = instanceAttrs+      samplerId+      textureId+      transforms++-- | GPU-bound data.+data InstanceBuffers textureStage transformStage = InstanceBuffers+  { ibTexture   :: InstanceTexture textureStage+  , ibTransform :: InstanceTransform transformStage+  }++type InstanceTexture stage = Buffer.Allocated stage TextureParams++type InstanceTransform stage = Buffer.Allocated stage Transform++instance Model.HasVertexBuffers (InstanceBuffers textureStage transformStage) where+  type VertexBuffersOf (InstanceBuffers textureStage transformStage) = InstanceAttrs++  {-# INLINE getVertexBuffers #-}+  getVertexBuffers InstanceBuffers{..} =+    [ Buffer.aBuffer ibTexture+    , Buffer.aBuffer ibTransform+    ]++  {-# INLINE getInstanceCount #-}+  getInstanceCount InstanceBuffers{..} =+    min+      (Buffer.aUsed ibTexture)+      (Buffer.aUsed ibTransform)++data TextureParams = TextureParams+  { tpScale     :: Vec2+  , tpOffset    :: Vec2+  , tpGamma     :: Vec4+  , tpSamplerId :: Int32+  , tpTextureId :: Int32+  }+  deriving (Show)++instance Zero TextureParams where+  zero = TextureParams+    { tpScale     = 1+    , tpOffset    = 0+    , tpGamma     = 1.0+    , tpSamplerId = minBound+    , tpTextureId = minBound+    }++instance Storable TextureParams where+  alignment ~_ = 4++  sizeOf ~_ = 8 + 8 + 16 + 4 + 4++  poke ptr TextureParams{..} = do+    pokeByteOff ptr  0 tpScale+    pokeByteOff ptr  8 tpOffset+    pokeByteOff ptr 16 tpGamma+    pokeByteOff ptr 32 tpSamplerId+    pokeByteOff ptr 36 tpTextureId++  peek ptr = do+    tpScale     <- peekByteOff ptr  0+    tpOffset    <- peekByteOff ptr  8+    tpGamma     <- peekByteOff ptr 16+    tpSamplerId <- peekByteOff ptr 32+    tpTextureId <- peekByteOff ptr 36+    pure TextureParams{..}++vkInstanceTexture :: [Vk.Format]+vkInstanceTexture =+  [ Vk.FORMAT_R32G32B32A32_SFLOAT -- iTextureScaleOffset :: vec4+  , Vk.FORMAT_R32G32B32A32_SFLOAT -- iTextureGamma       :: vec4+  , Vk.FORMAT_R32G32_SINT         -- iTextureIds         :: ivec2+  ]++allocateInstancesWith+  :: ( MonadResource m+     , MonadUnliftIO m+     )+  => (Vk.BufferUsageFlagBits -> Int -> Storable.Vector TextureParams -> m (InstanceTexture texture))+  -> (Vk.BufferUsageFlagBits -> Int -> Storable.Vector Transform -> m (InstanceTransform transform))+  -> (forall stage a . Buffer.Allocated stage a -> m ())+  -> [InstanceAttrs]+  -> m (ReleaseKey, InstanceBuffers texture transform)+allocateInstancesWith createTextures createTransforms bufferDestroy instances = do+  ul <- askUnliftIO+  allocate (create ul) (destroy ul)+  where+    textures   = Storable.fromList $ map textureParams instances+    transforms = Storable.fromList $ map transformMat4 instances+    numInstances = Storable.length textures++    create (UnliftIO ul) = ul do+      ibTexture   <- createTextures   Vk.BUFFER_USAGE_VERTEX_BUFFER_BIT numInstances textures+      ibTransform <- createTransforms Vk.BUFFER_USAGE_VERTEX_BUFFER_BIT numInstances transforms+      pure InstanceBuffers{..}++    destroy (UnliftIO ul) InstanceBuffers{..} = ul do+      bufferDestroy ibTexture+      bufferDestroy ibTransform++allocateInstancesCoherent+  :: ( MonadReader env m+     , HasVulkan env+     , MonadResource m+     , MonadUnliftIO m+     )+  => [InstanceAttrs]+  -> m (ReleaseKey, InstanceBuffers 'Buffer.Coherent 'Buffer.Coherent)+allocateInstancesCoherent instances = do+  context <- ask+  allocateInstancesWith+    (Buffer.createCoherent context)+    (Buffer.createCoherent context)+    (Buffer.destroy context)+    instances++allocateInstancesCoherent_+  :: (HasVulkan env)+  => Int+  -> ResourceT (RIO env) (InstanceBuffers 'Buffer.Coherent 'Buffer.Coherent)+allocateInstancesCoherent_ n =+  fmap snd $ allocateInstancesCoherent (replicate n zero)++updateCoherentResize_+  :: ( HasVulkan context+     , MonadUnliftIO m+     )+  => context+  -> InstanceBuffers 'Buffer.Coherent 'Buffer.Coherent+  -> (Storable.Vector TextureParams, Storable.Vector Transform)+  -> m (InstanceBuffers 'Buffer.Coherent 'Buffer.Coherent)+updateCoherentResize_ context InstanceBuffers{..} (textures, transforms) =+  InstanceBuffers+    <$> Buffer.updateCoherentResize_ context ibTexture textures+    <*> Buffer.updateCoherentResize_ context ibTransform transforms
+ src/Render/Debug/Pipeline.hs view
@@ -0,0 +1,151 @@+module Render.Debug.Pipeline+  ( Pipeline+  , allocate+  ) where++import RIO++import Control.Monad.Trans.Resource (ResourceT)+import Data.Tagged (Tagged(..))+import Vulkan.Core10 qualified as Vk+import Vulkan.Zero (zero)++import Engine.Vulkan.Pipeline qualified as Pipeline+import Engine.Vulkan.Types (HasVulkan, HasRenderPass(..), DsBindings)+import Render.Code (compileVert, compileFrag, glsl)+import Render.Debug.Model qualified as Model+import Render.DescSets.Set0 (Scene, vertexPos, instanceTransform)+import Render.DescSets.Set0.Code (set0binding0, set0binding1, set0binding2, set0binding3, set0binding5color)++type Pipeline = Pipeline.Pipeline '[Scene] Model.VertexAttrs Model.InstanceAttrs++allocate+  :: ( HasVulkan env+     , HasRenderPass renderpass+     )+  => Vk.SampleCountFlagBits+  -> Tagged Scene DsBindings+  -> renderpass+  -> ResourceT (RIO env) Pipeline+allocate multisample (Tagged set0) rp = do+  (_, p) <- Pipeline.allocate+    Nothing+    multisample+    (zero+      { Pipeline.cDescLayouts  = Tagged @'[Scene] [set0]+      , Pipeline.cVertexCode   = Just vertCode+      , Pipeline.cVertexInput  = vertexInput+      , Pipeline.cFragmentCode = Just fragCode+      })+    rp+  pure p+  where+    vertexInput = Pipeline.vertexInput+      [ vertexPos -- vPosition+      , (Vk.VERTEX_INPUT_RATE_VERTEX,   Model.vkVertexAttrs)+      , (Vk.VERTEX_INPUT_RATE_INSTANCE, Model.vkInstanceTexture)+      , instanceTransform+      ]++vertCode :: ByteString+vertCode =+  $(compileVert [glsl|+    #version 450+    #extension GL_ARB_separate_shader_objects : enable++    ${set0binding0}++    // vertexPos+    layout(location = 0) in vec3 vPosition;+    // vertexAttrs+    layout(location = 1) in vec2 vTexCoord;+    // textureParams+    layout(location = 2) in  vec4 iTextureScaleOffset;+    layout(location = 3) in  vec4 iTextureGamma;+    layout(location = 4) in ivec2 iTextureIds;++    // transformMat+    layout(location = 5) in mat4 iModel;++    layout(location = 0)      out  vec2 fTexCoord;+    layout(location = 1) flat out  vec4 fTextureGamma;+    layout(location = 2) flat out ivec2 fTextureIds;++    void main() {+      gl_Position+        = scene.projection+        * scene.view+        * iModel+        * vec4(vPosition, 1.0);++      fTexCoord     = vTexCoord * iTextureScaleOffset.st + iTextureScaleOffset.pq;+      fTextureGamma = iTextureGamma;+      fTextureIds   = iTextureIds;+    }+  |])++fragCode :: ByteString+fragCode =+  $(compileFrag [glsl|+    #version 450+    #extension GL_ARB_separate_shader_objects : enable+    #extension GL_EXT_nonuniform_qualifier : enable++    ${set0binding1}+    ${set0binding2}+    ${set0binding3}+    ${set0binding5color}++    layout(location = 0)      in  vec2 fTexCoord;+    layout(location = 1) flat in  vec4 fTextureGamma;+    layout(location = 2) flat in ivec2 fTextureIds;++    layout(location = 0) out vec4 oColor;++    const int mode = 1;++    void main() {+      vec4 texel = vec4(0);++      switch(mode) {+        case 0:+          texel = vec4(fTexCoord, 0, 1);+          break;++        case 1:+          if ((fTextureIds.t < 0) || (fTextureIds.s < 0)) break;++          texel = texture(+            sampler2D(+              textures[nonuniformEXT(fTextureIds.t)],+              samplers[nonuniformEXT(fTextureIds.s)]+            ),+            fTexCoord+          );+          break;++        case 2:+          float d0 = texture(+            shadowmaps,+            vec3(fTexCoord, 0.0)+          ).x;++          float d1 = texture(+            shadowmaps,+            vec3(fTexCoord, 1.0)+          ).x;++          texel = vec4(1-d0, 0, 1-d1, 1.0);+          break;++        default:+          break;+      }++      vec3 color = pow(texel.rgb, fTextureGamma.rgb);+      float combinedAlpha = texel.a * fTextureGamma.a;++      // XXX: premultiply alpha due to additive blending+      oColor = vec4(color * combinedAlpha, combinedAlpha);+    }+  |])
+ src/Render/DescSets/Set0.hs view
@@ -0,0 +1,511 @@+{-# OPTIONS_GHC -fplugin Foreign.Storable.Generic.Plugin #-}++module Render.DescSets.Set0+  ( Scene(..)+  , emptyScene++  , allocate+  , allocateEmpty++  , updateSet0Ds++  , set0+  , set0_++  -- TODO: extract to typeclass magic+  , vertexPos+  , instanceTransform++  , FrameResource(..)+  , extendResourceDS++  , Buffer+  , Process+  , observe++  , withBoundSet0+  ) where++import RIO++import Control.Monad.Trans.Resource (ResourceT)+import Control.Monad.Trans.Resource qualified as ResourceT+import Data.Bits ((.|.))+import Data.ByteString.Char8 qualified as BS8+import Data.Kind (Type)+import Data.Tagged (Tagged(..))+import Data.Vector qualified as Vector+import Data.Vector.Storable qualified as VectorS+import Foreign.Storable.Generic (GStorable)+import Geomancy (Vec3, Vec4, vec3)+import Geomancy.Transform (Transform)+import Vulkan.Core10 qualified as Vk+import Vulkan.Core12.Promoted_From_VK_EXT_descriptor_indexing qualified as Vk12+import Vulkan.CStruct.Extends (SomeStruct(..))+import Vulkan.NamedType ((:::))+import Vulkan.Utils.Debug qualified as Debug+import Vulkan.Zero (Zero(..))++import Engine.Types (StageRIO)+import Engine.Vulkan.DescSets (Bound, Extend, extendDS, withBoundDescriptorSets0)+import Engine.Vulkan.Pipeline (Pipeline)+import Engine.Vulkan.Pipeline qualified as Pipeline+import Engine.Vulkan.Types (DsBindings, HasVulkan(..))+import Engine.Worker qualified as Worker+import Render.DescSets.Sun (Sun)+import Render.Lit.Material (Material)+import Resource.Buffer qualified as Buffer+import Resource.Collection qualified as Collection+import Resource.DescriptorSet qualified as DescriptorSet+import Resource.Image qualified as Image+import Resource.Texture (Flat, CubeMap, Texture)+import Resource.Texture qualified as Texture++-- * Set0 data++data Scene = Scene+  { sceneProjection    :: Transform+  , sceneInvProjection :: Transform++  , sceneView          :: Transform+  , sceneInvView       :: Transform+  , sceneViewPos       :: Vec3 -- XXX: gets extra padding+  , sceneViewDir       :: Vec3 -- XXX: gets extra padding++  , sceneTweaks        :: Vec4 -- ^ 4 debug tweaks bound to Kontrol++  , sceneFog           :: Vec4 -- XXX: RGB color + scatter factor β+  , sceneEnvCube       :: Int32+  , sceneNumLights     :: Word32+  }+  deriving (Show, Generic)++instance GStorable Scene++emptyScene :: Scene+emptyScene = Scene+  { sceneProjection    = mempty+  , sceneInvProjection = mempty++  , sceneView          = mempty+  , sceneInvView       = mempty+  , sceneViewPos       = vec3 0 0 0+  , sceneViewDir       = vec3 0 0 1++  , sceneFog           = 0+  , sceneEnvCube       = minBound+  , sceneNumLights     = 0++  , sceneTweaks        = 0+  }++-- * Common descriptor set++set0+  :: Traversable samplers+  => samplers Vk.Sampler+  -> Either Word32 (Vector (Texture Flat))+  -> Either Word32 (Vector (Texture CubeMap))+  -> Word32+  -> Tagged Scene DsBindings+set0 samplers textures cubes shadows = Tagged+  [ (set0bind0,          zero)+  , (set0bind1 samplers, zero)+  , (set0bind2 textures, partialBinding)+  , (set0bind3 cubes,    partialBinding)+  , (set0bind4,          zero)+  , (set0bind5 shadows,  partialBinding)+  , (set0bind6,          zero)+  ]+  where+    partialBinding =+      Vk12.DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT++    _partialVariable =+      partialBinding .|.+      Vk12.DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT+      {-+        VUID-VkDescriptorSetLayoutBindingFlagsCreateInfo-pBindingFlags-03004(ERROR / SPEC):+          msgNum: 222246202 - Validation Error:+          [ VUID-VkDescriptorSetLayoutBindingFlagsCreateInfo-pBindingFlags-03004 ]+          Object 0: handle = 0x157ea80, type = VK_OBJECT_TYPE_DEVICE;+          | MessageID = 0xd3f353a+          | Invalid flags for VkDescriptorSetLayoutBinding entry 2 The Vulkan spec states:+              If an element of pBindingFlags includes VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT,+              then all other elements of VkDescriptorSetLayoutCreateInfo::pBindings must have+              a smaller value of binding+              (https://vulkan.lunarg.com/doc/view/1.2.162.1~rc2/linux/1.2-extensions/vkspec.html#VUID-VkDescriptorSetLayoutBindingFlagsCreateInfo-pBindingFlags-03004)+      -}++-- | Nothing texture-related is provided.+set0_ :: Tagged Scene DsBindings+set0_ = set0 [] (Left 0) (Left 0) 0++set0bind0 :: Vk.DescriptorSetLayoutBinding+set0bind0 = Vk.DescriptorSetLayoutBinding+  { binding           = 0+  , descriptorType    = Vk.DESCRIPTOR_TYPE_UNIFORM_BUFFER+  , descriptorCount   = 1+  , stageFlags        = Vk.SHADER_STAGE_ALL+  , immutableSamplers = mempty+  }++set0bind1 :: Traversable t => t Vk.Sampler -> Vk.DescriptorSetLayoutBinding+set0bind1 samplers = Vk.DescriptorSetLayoutBinding+  { Vk.binding           = 1+  , Vk.stageFlags        = Vk.SHADER_STAGE_FRAGMENT_BIT+  , Vk.descriptorType    = Vk.DESCRIPTOR_TYPE_SAMPLER+  , Vk.descriptorCount   = fromIntegral $ Vector.length linearSamplers+  , Vk.immutableSamplers = linearSamplers+  }+  where+    linearSamplers = Collection.toVector samplers++set0bind2 :: Either Word32 (Vector (Texture Flat)) -> Vk.DescriptorSetLayoutBinding+set0bind2 textures = Vk.DescriptorSetLayoutBinding+  { binding           = 2+  , descriptorType    = Vk.DESCRIPTOR_TYPE_SAMPLED_IMAGE+  , descriptorCount   = textureCount+  , stageFlags        = Vk.SHADER_STAGE_FRAGMENT_BIT+  , immutableSamplers = mempty+  }+  where+    textureCount = case textures of+      Left n ->+        n+      Right v ->+        fromIntegral $ Vector.length v++set0bind3 :: Either Word32 (Vector (Texture CubeMap)) -> Vk.DescriptorSetLayoutBinding+set0bind3 cubes = Vk.DescriptorSetLayoutBinding+  { binding           = 3+  , descriptorType    = Vk.DESCRIPTOR_TYPE_SAMPLED_IMAGE+  , descriptorCount   = cubeCount+  , stageFlags        = Vk.SHADER_STAGE_FRAGMENT_BIT+  , immutableSamplers = mempty+  }+  where+    cubeCount = case cubes of+      Left n ->+        n+      Right v ->+        fromIntegral $ Vector.length v++set0bind4 :: Vk.DescriptorSetLayoutBinding+set0bind4 = Vk.DescriptorSetLayoutBinding+  { binding           = 4+  , descriptorType    = Vk.DESCRIPTOR_TYPE_UNIFORM_BUFFER+  , stageFlags        = Vk.SHADER_STAGE_FRAGMENT_BIT+  , descriptorCount   = 1+  , immutableSamplers = mempty+  }++set0bind5 :: Word32 -> Vk.DescriptorSetLayoutBinding+set0bind5 shadows = Vk.DescriptorSetLayoutBinding+  { binding           = 5+  , descriptorType    = Vk.DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER+  , stageFlags        = Vk.SHADER_STAGE_FRAGMENT_BIT+  , descriptorCount   = shadows+  , immutableSamplers = mempty+  }++set0bind6 :: Vk.DescriptorSetLayoutBinding+set0bind6 = Vk.DescriptorSetLayoutBinding+  { binding           = 6+  , descriptorType    = Vk.DESCRIPTOR_TYPE_UNIFORM_BUFFER+  , stageFlags        = Vk.SHADER_STAGE_FRAGMENT_BIT+  , descriptorCount   = 1+  , immutableSamplers = mempty+  }++vertexPos :: (Vk.VertexInputRate, [Vk.Format])+vertexPos =+  ( Vk.VERTEX_INPUT_RATE_VERTEX+  , [ Vk.FORMAT_R32G32B32_SFLOAT -- vPosition :: vec3+    ]+  )++instanceTransform :: (Vk.VertexInputRate, [Vk.Format])+instanceTransform =+  ( Vk.VERTEX_INPUT_RATE_INSTANCE+  , [ Vk.FORMAT_R32G32B32A32_SFLOAT -- iModel :: mat4+    , Vk.FORMAT_R32G32B32A32_SFLOAT+    , Vk.FORMAT_R32G32B32A32_SFLOAT+    , Vk.FORMAT_R32G32B32A32_SFLOAT+    ]+  )++-- * Setup++allocate+  :: (Traversable textures, Traversable cubes)+  => Tagged '[Scene] Vk.DescriptorSetLayout+  -> textures (Texture.Texture Texture.Flat)+  -> cubes (Texture.Texture Texture.CubeMap)+  -> Maybe (Buffer.Allocated 'Buffer.Coherent Sun)+  -> "shadow maps" ::: Vector Vk.ImageView+  -> Maybe (Buffer.Allocated 'Buffer.Coherent Material)+  -> ResourceT (StageRIO st) (FrameResource '[Scene])+allocate (Tagged set0layout) textures cubes lightsData shadowViews materialsData = do+  context <- asks id++  (_dpKey, descPool) <- DescriptorSet.allocatePool 1 dpSizes++  let+    set0dsCI = zero+      { Vk.descriptorPool = descPool+      , Vk.setLayouts     = Vector.singleton set0layout+      }+  descSets <- fmap (Tagged @'[Scene]) $+    Vk.allocateDescriptorSets (getDevice context) set0dsCI++  (_, sceneData) <- ResourceT.allocate+    (Buffer.createCoherent context Vk.BUFFER_USAGE_UNIFORM_BUFFER_BIT 1 $ VectorS.singleton emptyScene)+    (Buffer.destroy context)++  let+    -- TODO: must be checked against depth format and TILING_OPTIMAL+    -- shadowFilter = Vk.FILTER_NEAREST+    shadowFilter = Vk.FILTER_LINEAR++    shadowCI = zero+      { Vk.addressModeU  = Vk.SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE+      , Vk.addressModeV  = Vk.SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE+      , Vk.addressModeW  = Vk.SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE+      , Vk.borderColor   = Vk.BORDER_COLOR_FLOAT_OPAQUE_WHITE+      , Vk.magFilter     = shadowFilter+      , Vk.minFilter     = shadowFilter+      , Vk.compareEnable = True+      , Vk.compareOp     = Vk.COMPARE_OP_LESS+      }++  let ifor = flip Vector.imapM+  shadowMaps <- ifor shadowViews \ix depthView -> do+    (_, shadowSampler) <- Vk.withSampler (getDevice context) shadowCI Nothing ResourceT.allocate+    Debug.nameObject (getDevice context) shadowSampler $+      "ShadowSampler." <> BS8.pack (show ix)+    pure (shadowSampler, depthView)++  updateSet0Ds context descSets sceneData textures cubes lightsData shadowMaps materialsData++  scene <- Worker.newObserverIO emptyScene++  pure $ FrameResource descSets sceneData scene++dpSizes :: DescriptorSet.TypeMap Word32+dpSizes =+  [ ( Vk.DESCRIPTOR_TYPE_UNIFORM_BUFFER+    , uniformBuffers+    )+  , ( Vk.DESCRIPTOR_TYPE_SAMPLED_IMAGE+    , sampledImages+    )+  , ( Vk.DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER+    , sampledImages + shadowMaps+    )+  , ( Vk.DESCRIPTOR_TYPE_SAMPLER+    , staticSamplers+    )+  ]+  where+    uniformBuffers = 3    -- 1 scene + 1 light array + 1 material array+    sampledImages  = 128  -- max dynamic textures and cubemaps+    staticSamplers = 8    -- immutable samplers+    shadowMaps     = 2    -- max shadowmaps++-- | Minimal viable 'Scene' without textures and lighting.+allocateEmpty+  :: Tagged '[Scene] Vk.DescriptorSetLayout+  -> ResourceT (StageRIO st) (FrameResource '[Scene])+allocateEmpty taggedLayout = allocate taggedLayout [] [] Nothing mempty Nothing++updateSet0Ds+  :: (HasVulkan context, Traversable textures, Traversable cubes)+  => context+  -> Tagged '[Scene] (Vector Vk.DescriptorSet)+  -> Buffer.Allocated 'Buffer.Coherent Scene+  -> textures (Texture.Texture Texture.Flat)+  -> cubes (Texture.Texture Texture.CubeMap)+  -> Maybe (Buffer.Allocated 'Buffer.Coherent Sun)+  -> Vector (Vk.Sampler, Vk.ImageView)+  -> Maybe (Buffer.Allocated 'Buffer.Coherent Material)+  -> ResourceT (StageRIO st) ()+updateSet0Ds context (Tagged ds) sceneData textures cubes lightsData shadowMaps materialsData =+  Vk.updateDescriptorSets (getDevice context) writeSets mempty++  where+    linearTextures = Collection.toVector textures+    linearCubes = Collection.toVector cubes++    destSet0 = case Vector.headM ds of+      Nothing ->+        error "assert: descriptor sets promised to contain [Scene]"+      Just one ->+        one++    writeSet0b0 = SomeStruct zero+      { Vk.dstSet          = destSet0+      , Vk.dstBinding      = 0+      , Vk.dstArrayElement = 0+      , Vk.descriptorCount = 1+      , Vk.descriptorType  = Vk.DESCRIPTOR_TYPE_UNIFORM_BUFFER+      , Vk.bufferInfo      = Vector.singleton set0bind0I+      }+      where+        set0bind0I = Vk.DescriptorBufferInfo+          { Vk.buffer = Buffer.aBuffer sceneData+          , Vk.offset = 0+          , Vk.range  = Vk.WHOLE_SIZE+          }++    writeSet0b2 = SomeStruct zero+      { Vk.dstSet          = destSet0+      , Vk.dstBinding      = 2+      , Vk.descriptorType  = Vk.DESCRIPTOR_TYPE_SAMPLED_IMAGE+      , Vk.dstArrayElement = 0+      , Vk.descriptorCount = fromIntegral $ Vector.length linearTextures+      , Vk.imageInfo       = textureInfos+      }+      where+        textureInfos = do+          texture <- linearTextures+          pure Vk.DescriptorImageInfo+            { sampler     = zero+            , imageView   = Image.aiImageView $ Texture.tAllocatedImage texture+            , imageLayout = Vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL+            }++    writeSet0b3 = SomeStruct zero+      { Vk.dstSet          = destSet0+      , Vk.dstBinding      = 3+      , Vk.descriptorType  = Vk.DESCRIPTOR_TYPE_SAMPLED_IMAGE+      , Vk.dstArrayElement = 0+      , Vk.descriptorCount = fromIntegral $ Vector.length linearCubes+      , Vk.imageInfo       = cubeInfos+      }+      where+        cubeInfos = do+          cube <- linearCubes+          pure Vk.DescriptorImageInfo+            { sampler     = zero+            , imageView   = Image.aiImageView $ Texture.tAllocatedImage cube+            , imageLayout = Vk.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL+            }++    writeSet0b4M =+      case lightsData of+        Nothing ->+          mzero+        Just someLights ->+          pure $ SomeStruct zero+            { Vk.dstSet          = destSet0+            , Vk.dstBinding      = 4+            , Vk.dstArrayElement = 0+            , Vk.descriptorCount = 1+            , Vk.descriptorType  = Vk.DESCRIPTOR_TYPE_UNIFORM_BUFFER+            , Vk.bufferInfo      = Vector.singleton set0bind4I+            }+          where+            set0bind4I = Vk.DescriptorBufferInfo+              { Vk.buffer = Buffer.aBuffer someLights+              , Vk.offset = 0+              , Vk.range  = Vk.WHOLE_SIZE+              }++    writeSet0b5 = SomeStruct zero+      { Vk.dstSet          = destSet0+      , Vk.dstBinding      = 5+      , Vk.descriptorType  = Vk.DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER+      , Vk.dstArrayElement = 0+      , Vk.descriptorCount = fromIntegral $ Vector.length shadowMaps+      , Vk.imageInfo       = shadowInfos+      }+      where+        shadowInfos = do+          (shadowSampler, shadowImageView) <- shadowMaps+          pure Vk.DescriptorImageInfo+            { sampler     = shadowSampler+            , imageView   = shadowImageView+            , imageLayout = Vk.IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL+            }++    writeSet0b6M =+      case materialsData of+        Nothing ->+          mzero+        Just someMaterials ->+          pure $ SomeStruct zero+            { Vk.dstSet          = destSet0+            , Vk.dstBinding      = 6+            , Vk.dstArrayElement = 0+            , Vk.descriptorCount = 1+            , Vk.descriptorType  = Vk.DESCRIPTOR_TYPE_UNIFORM_BUFFER+            , Vk.bufferInfo      = Vector.singleton set0bind6I+            }+          where+            set0bind6I = Vk.DescriptorBufferInfo+              { Vk.buffer = Buffer.aBuffer someMaterials+              , Vk.offset = 0+              , Vk.range  = Vk.WHOLE_SIZE+              }++    writeSets = Vector.fromList $ concat+      [ pure writeSet0b0+      -- XXX: binding 1 is immutable samplers, baked into layout.+      , skipEmpty linearTextures writeSet0b2+      , skipEmpty linearCubes writeSet0b3+      , writeSet0b4M+      , skipEmpty shadowMaps writeSet0b5+      , writeSet0b6M+      ]+      where+        skipEmpty items writer+          | Vector.null items = mempty+          | otherwise = pure writer++extendResourceDS+  :: FrameResource ds+  -> Tagged ext Vk.DescriptorSet+  -> FrameResource (Extend ds ext)+extendResourceDS FrameResource{..} ext = FrameResource+  { frDescSets = extendDS frDescSets ext+  , ..+  }++-- * Frame data++data FrameResource (ds :: [Type]) = FrameResource+  { frDescSets :: Tagged ds (Vector Vk.DescriptorSet)+  , frBuffer   :: Buffer+  , frObserver :: Worker.ObserverIO Scene+  }++type Buffer = Buffer.Allocated 'Buffer.Coherent Scene++-- | A process that will assemble 'Scene' values.+type Process = Worker.Merge Scene++observe+  :: (MonadUnliftIO m) -- TODO: compatible '[Scene]+  => Process -> FrameResource ds -> m ()+observe process FrameResource{frBuffer, frObserver}=+  Worker.observeIO_ process frObserver \_old new -> do+    _same <- Buffer.updateCoherent (VectorS.singleton new) frBuffer+    pure new++-- * Rendering++withBoundSet0+  :: MonadIO m+  => FrameResource ds+  -> Pipeline ds vertices instances+  -> Vk.CommandBuffer+  -> Bound ds Void Void m b+  -> m b+withBoundSet0 FrameResource{frDescSets} refPipeline cb action =+  withBoundDescriptorSets0+    cb+    Vk.PIPELINE_BIND_POINT_GRAPHICS+    (Pipeline.pLayout refPipeline)+    frDescSets+    action
+ src/Render/DescSets/Set0/Code.hs view
@@ -0,0 +1,76 @@+module Render.DescSets.Set0.Code+  ( set0binding0+  , set0binding1+  , set0binding2+  , set0binding3+  , set0binding4+  , set0binding5+  , set0binding5color+  , set0binding6+  ) where++import Render.Code (Code(..), trimming)++set0binding0 :: Code+set0binding0 = Code+  [trimming|+    layout(set=0, binding=0, std140) uniform Globals {+      mat4 projection;+      mat4 invProjection;+      mat4 view;+      mat4 invView;+      vec4 viewPosition;+      vec4 viewDirection;+      vec4 tweaks;+      vec4 fog;+       int envCubeId;+      uint numLights;+    } scene;+  |]++set0binding1 :: Code+set0binding1 = Code+  [trimming|+    layout(set=0, binding=1) uniform sampler samplers[];+  |]++set0binding2 :: Code+set0binding2 = Code+  [trimming|+    layout(set=0, binding=2) uniform texture2D textures[];+  |]++set0binding3 :: Code+set0binding3 = Code+  [trimming|+    layout(set=0, binding=3) uniform textureCube cubes[];+  |]++set0binding4 :: Code+set0binding4 = Code+  [trimming|+    layout(set=0, binding=4, std140) uniform Lights {+      Light lights[MAX_LIGHTS];+    };+  |]++set0binding5 :: Code+set0binding5 = Code+  [trimming|+    layout(set=0, binding=5) uniform sampler2DArrayShadow shadowmaps;+  |]++-- XXX: for shadowmap mode in Debug pipeline+set0binding5color :: Code+set0binding5color = Code+  [trimming|+    layout(set=0, binding=5) uniform sampler2DArray shadowmaps;+  |]++set0binding6 :: Code+set0binding6 = Code+  [trimming|+    layout(set=0, binding=6, std140) uniform Materials {+      Material materials[MAX_MATERIALS];+    };+  |]
+ src/Render/DescSets/Sun.hs view
@@ -0,0 +1,165 @@+{-# OPTIONS_GHC -fplugin Foreign.Storable.Generic.Plugin #-}++module Render.DescSets.Sun+  ( Sun(..)+  , createSet0Ds+  , set0++  , pattern MAX_VIEWS+  ) where++import RIO++import Control.Monad.Trans.Resource (ResourceT)+import Control.Monad.Trans.Resource qualified as ResourceT+import Data.Tagged (Tagged(..))+import Data.Vector qualified as Vector+import Data.Vector.Storable qualified as VectorS+import Foreign.Storable.Generic (GStorable)+import Geomancy (Vec4, vec4)+import Geomancy.Transform (Transform)+import Vulkan.Core10 qualified as Vk+import Vulkan.CStruct.Extends (SomeStruct(..))+import Vulkan.Utils.Debug qualified as Debug+import Vulkan.Zero (Zero(..))++import Engine.Types (StageRIO)+import Engine.Vulkan.DescSets ()+import Engine.Vulkan.Types (DsBindings, HasVulkan(..))+import Resource.Buffer qualified as Buffer+import Resource.DescriptorSet qualified as DescriptorSet++-- * Set0 data++-- | Maximum "guaranteed" amount for multiview passes+pattern MAX_VIEWS :: Int+pattern MAX_VIEWS = 6++data Sun = Sun+  { sunViewProjection :: Transform+  , sunShadow         :: Vec4 -- offsetx, offsety, index, size -- XXX: only index is used+  , sunPosition       :: Vec4 -- XXX: alpha available for stuff+  , sunDirection      :: Vec4 -- XXX: alpha available for stuff+  , sunColor          :: Vec4 -- XXX: RGB premultiplied, alpha is available for stuff+  }+  deriving (Show, Generic)++instance GStorable Sun++instance Zero Sun where+  zero = Sun+    { sunViewProjection = mempty+    , sunShadow         = 0+    , sunPosition       = 0+    , sunDirection      = vec4 0 1 0 0+    , sunColor          = 0+    }++-- * Shadow casting descriptor set++set0+  :: Tagged Sun DsBindings+set0 = Tagged+  [ (set0bind0, zero)+  ]++set0bind0 :: Vk.DescriptorSetLayoutBinding+set0bind0 = Vk.DescriptorSetLayoutBinding+  { binding           = 0+  , descriptorType    = Vk.DESCRIPTOR_TYPE_UNIFORM_BUFFER+  , descriptorCount   = 1+  , stageFlags        = Vk.SHADER_STAGE_VERTEX_BIT+  , immutableSamplers = mempty+  }++-- * Setup++createSet0Ds+  :: Tagged '[Sun] Vk.DescriptorSetLayout+  -> ResourceT (StageRIO st)+      ( Tagged '[Sun] (Vector Vk.DescriptorSet)+      , Buffer.Allocated 'Buffer.Coherent Sun+      )+createSet0Ds (Tagged set0layout) = do+  context <- asks id++  (_dpKey, descPool) <- DescriptorSet.allocatePool 1 dpSizes++  let+    set0dsCI = zero+      { Vk.descriptorPool = descPool+      , Vk.setLayouts     = Vector.singleton set0layout+      }+  descSets <- fmap (Tagged @'[Sun]) $+    Vk.allocateDescriptorSets (getDevice context) set0dsCI++  let+    initialSuns = VectorS.replicate MAX_VIEWS zero+  (_, sunData) <- ResourceT.allocate+    (Buffer.createCoherent context Vk.BUFFER_USAGE_UNIFORM_BUFFER_BIT MAX_VIEWS initialSuns)+    (Buffer.destroyAll context . Just)++  updateSet0Ds descSets sunData++  let device = getDevice context+  Debug.nameObject device descPool "Sun.Pool"+  for_ (unTagged descSets) \ds ->+    Debug.nameObject device ds "Sun.DS"+  Debug.nameObject device (Buffer.aBuffer sunData) "Sun.Data"++  pure (descSets, sunData)++dpSizes :: DescriptorSet.TypeMap Word32+dpSizes =+  [ ( Vk.DESCRIPTOR_TYPE_UNIFORM_BUFFER+    , uniformBuffers+    )+  -- XXX: may be required to fetch textures for shadows from texture-masked models+  -- , ( Vk.DESCRIPTOR_TYPE_SAMPLED_IMAGE+  --   , sampledImages+  --   )+  -- , ( Vk.DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER+  --   , sampledImages + shadowMaps+  --   )+  -- , ( Vk.DESCRIPTOR_TYPE_SAMPLER+  --   , staticSamplers+  --   )+  ]+  where+    uniformBuffers = 2    -- 1 scene + 1 light array+    -- sampledImages  = 128  -- max dynamic textures and cubemaps+    -- staticSamplers = 8    -- immutable samplers+    -- shadowMaps     = 2    -- max shadowmaps++updateSet0Ds+  :: Tagged '[Sun] (Vector Vk.DescriptorSet)+  -> Buffer.Allocated 'Buffer.Coherent Sun+  -> ResourceT (StageRIO st) ()+updateSet0Ds (Tagged ds) Buffer.Allocated{aBuffer} = do+  context <- asks id+  Vk.updateDescriptorSets (getDevice context) writeSets mempty++  where+    destSet0 = case Vector.headM ds of+      Nothing ->+        error "assert: descriptor sets promised to contain [Sun]"+      Just one ->+        one++    writeSet0b0 = SomeStruct zero+      { Vk.dstSet          = destSet0+      , Vk.dstBinding      = 0+      , Vk.dstArrayElement = 0+      , Vk.descriptorCount = 1+      , Vk.descriptorType  = Vk.DESCRIPTOR_TYPE_UNIFORM_BUFFER+      , Vk.bufferInfo      = Vector.singleton set0bind0I+      }+      where+        set0bind0I = Vk.DescriptorBufferInfo+          { Vk.buffer = aBuffer+          , Vk.offset = 0+          , Vk.range  = Vk.WHOLE_SIZE+          }++    writeSets =+      Vector.singleton writeSet0b0
+ src/Render/Font/EvanwSdf/Model.hs view
@@ -0,0 +1,75 @@+module Render.Font.EvanwSdf.Model+  ( Model+  , VertexAttrs+  , InstanceAttrs(..)+  , vkInstanceAttrs+  , InstanceBuffer+  ) where++import RIO++import Foreign (Storable(..))+import Geomancy (Vec2, Vec4)+import Geomancy.Vec3 qualified as Vec3+import Vulkan.Core10 qualified as Vk+import Vulkan.NamedType ((:::))++import Resource.Buffer qualified as Buffer+import Resource.Model qualified as Model++type Model buf = Model.Indexed buf Vec3.Packed VertexAttrs++type VertexAttrs = "uv" ::: Vec2++data InstanceAttrs = InstanceAttrs+  { vertRect     :: Vec4+  , fragRect     :: Vec4+  , color        :: Vec4+  , outlineColor :: Vec4++  , samplerId    :: Int32+  , textureId    :: Int32++  , smoothing    :: Float+  , outlineWidth :: Float+  }+  deriving (Eq, Show)++vkInstanceAttrs :: [Vk.Format]+vkInstanceAttrs =+  [ Vk.FORMAT_R32G32B32A32_SFLOAT -- Quad scale+offset+  , Vk.FORMAT_R32G32B32A32_SFLOAT -- UV scale+offset+  , Vk.FORMAT_R32G32B32A32_SFLOAT -- Color+  , Vk.FORMAT_R32G32B32A32_SFLOAT -- Outline color++  , Vk.FORMAT_R32G32_SINT         -- Sampler + texture IDs++  , Vk.FORMAT_R32G32_SFLOAT       -- Smoothing + outline width+  ]++instance Storable InstanceAttrs where+  alignment ~_ = 4+  sizeOf ~_ = 16 + 16 + 16 + 16 + 4 + 4 + 4 + 4++  peek ptr = do+    vertRect     <- peekByteOff ptr  0 -- +16+    fragRect     <- peekByteOff ptr 16 -- +16+    color        <- peekByteOff ptr 32 -- +16+    outlineColor <- peekByteOff ptr 48 -- +16+    samplerId    <- peekByteOff ptr 64 -- +4+    textureId    <- peekByteOff ptr 68 -- +4+    smoothing    <- peekByteOff ptr 72 -- +4+    outlineWidth <- peekByteOff ptr 76 -- +4+    pure InstanceAttrs{..}++  poke ptr InstanceAttrs{..} = do+    pokeByteOff ptr  0 vertRect+    pokeByteOff ptr 16 fragRect+    pokeByteOff ptr 32 color+    pokeByteOff ptr 48 outlineColor+    pokeByteOff ptr 64 samplerId+    pokeByteOff ptr 68 textureId+    pokeByteOff ptr 72 smoothing+    pokeByteOff ptr 76 outlineWidth++type InstanceBuffer stage = Buffer.Allocated stage InstanceAttrs
+ src/Render/Font/EvanwSdf/Pipeline.hs view
@@ -0,0 +1,157 @@+{-# LANGUAGE OverloadedLists #-}++module Render.Font.EvanwSdf.Pipeline+  ( Pipeline+  , allocate+  ) where++import RIO++import Control.Monad.Trans.Resource (ResourceT)+import Data.Tagged (Tagged(..))+import Vulkan.Core10 qualified as Vk+import Vulkan.Zero (Zero(..))++import Engine.Vulkan.Pipeline (Config(..))+import Engine.Vulkan.Pipeline qualified as Pipeline+import Engine.Vulkan.Types (HasVulkan, HasRenderPass(..), DsBindings)+import Render.Code (compileVert, compileFrag, glsl)+import Render.DescSets.Set0 (Scene)+import Render.DescSets.Set0.Code (set0binding0, set0binding1, set0binding2)+import Render.Font.EvanwSdf.Model qualified as Model++type Pipeline = Pipeline.Pipeline '[Scene] () Model.InstanceAttrs++allocate+  :: ( HasVulkan env+     , HasRenderPass renderpass+     )+  => Vk.SampleCountFlagBits+  -> Tagged Scene DsBindings+  -> renderpass+  -> ResourceT (RIO env) Pipeline+allocate multisample (Tagged set0) = do+  fmap snd . Pipeline.allocate+    Nothing+    multisample+    zero+      { cVertexCode         = Just vertCode+      , cFragmentCode       = Just fragCode+      , cDescLayouts        = Tagged @'[Scene] [set0]+      , cVertexInput        = vertexInput+      , cDepthTest          = False+      , cDepthWrite         = False+      , cBlend              = True+      , cCull               = Vk.CULL_MODE_NONE+      }+  where+    vertexInput = Pipeline.vertexInput+      [ (Vk.VERTEX_INPUT_RATE_INSTANCE, Model.vkInstanceAttrs)+      ]++vertCode :: ByteString+vertCode =+  $(compileVert [glsl|+    #version 450+    #extension GL_ARB_separate_shader_objects : enable++    ${set0binding0}++    layout(location = 0) in  vec4 iVert;+    layout(location = 1) in  vec4 iFrag;+    layout(location = 2) in  vec4 iColor;+    layout(location = 3) in  vec4 iOutlineColor;+    layout(location = 4) in ivec2 iTextureIds;+    layout(location = 5) in  vec2 iSdf;++    layout(location = 0)      out  vec2 fTexCoord;+    layout(location = 1) flat out  vec4 fColor;+    layout(location = 2) flat out  vec4 fOutlineColor;+    layout(location = 3) flat out ivec2 fTextureIds;+    layout(location = 4) flat out  vec2 fSdf;++    vec2 positions[6] = vec2[](+      vec2(-0.5, 0.5),+      vec2(0.5, 0.5),+      vec2(-0.5, -0.5),++      vec2(0.5, -0.5),+      vec2(-0.5, -0.5),+      vec2(0.5, 0.5)+    );++    vec2 texCoords[6] = vec2[](+      vec2(0.0, 0.0),+      vec2(1.0, 0.0),+      vec2(0.0, 1.0),++      vec2(1.0, 1.0),+      vec2(0.0, 1.0),+      vec2(1.0, 0.0)+    );++    void main() {+      vec2 pos = positions[gl_VertexIndex];++      gl_Position+        = scene.projection+        * scene.view+      // TODO: * iModel+        * vec4(iVert.xy + pos * iVert.zw, 0, 1.0);++      vec2 uv = texCoords[gl_VertexIndex];+      fTexCoord = iFrag.xy + uv * iFrag.zw;++      fColor = iColor;+      fOutlineColor = iOutlineColor;+      fTextureIds = iTextureIds;+      fSdf = iSdf;+    }+  |])++fragCode :: ByteString+fragCode =+  $(compileFrag [glsl|+    #version 450+    #extension GL_ARB_separate_shader_objects : enable+    #extension GL_EXT_nonuniform_qualifier : enable++    ${set0binding1}+    ${set0binding2}++    layout(location = 0)      in  vec2 fTexCoord;+    layout(location = 1) flat in  vec4 fColor;+    layout(location = 2) flat in  vec4 fOutlineColor;+    layout(location = 3) flat in ivec2 fTextureIds;+    layout(location = 4) flat in  vec2 fSdf;++    layout(location = 0) out vec4 outColor;++    void main() {+      float sdf = texture(+        sampler2D(+          textures[nonuniformEXT(fTextureIds.t)],+          samplers[nonuniformEXT(fTextureIds.s)]+        ),+        fTexCoord+      ).r;++      float smoothing    = fSdf[0];+      float outlineWidth = fSdf[1];++      float outerEdgeCenter = 0.5 - outlineWidth;++      float alpha = smoothstep(+        outerEdgeCenter - smoothing,+        outerEdgeCenter + smoothing,+        sdf+      );+      float border = smoothstep(+        0.5 - smoothing,+        0.5 + smoothing,+        sdf+      );+      outColor = mix(fOutlineColor, fColor, border);+      outColor *= alpha;+    }+  |])
+ src/Render/ForwardMsaa.hs view
@@ -0,0 +1,287 @@+module Render.ForwardMsaa where++import RIO++import Control.Monad.Trans.Resource qualified as Resource+import Data.Bits ((.|.))+import Data.Vector qualified as Vector+import RIO.Vector.Partial ((!))+import Vulkan.Core10 qualified as Vk+import Vulkan.Utils.Debug qualified as Debug+import Vulkan.Zero (zero)++import Engine.Types.RefCounted (RefCounted, newRefCounted, releaseRefCounted, resourceTRefCount)+import Engine.Vulkan.Types (HasVulkan(..), HasSwapchain(..), HasRenderPass(..), RenderPass(..), MonadVulkan)+import Resource.Image (AllocatedImage)+import Resource.Image qualified as Image++-- * Simple MSAA-enabled pass++data ForwardMsaa = ForwardMsaa+  { fmRenderPass   :: Vk.RenderPass+  , fmColor        :: AllocatedImage+  , fmDepth        :: AllocatedImage+  , fmFrameBuffers :: Vector Vk.Framebuffer+  , fmRenderArea   :: Vk.Rect2D+  , fmClear        :: Vector Vk.ClearValue+  , fmRelease      :: RefCounted+  }++instance HasRenderPass ForwardMsaa where+  getRenderPass          = fmRenderPass+  getFramebuffers        = fmFrameBuffers+  getClearValues         = fmClear+  getRenderArea          = fmRenderArea++instance RenderPass ForwardMsaa where+  allocateRenderpass_ = allocateMsaa -- error "Thou shalt allocateMsaa instead"++  updateRenderpass = updateMsaa++  refcountRenderpass = resourceTRefCount . fmRelease++allocateMsaa+  :: ( Resource.MonadResource m+     , MonadVulkan env m+     , HasLogFunc env+     , HasSwapchain context+     )+  => context+  -> m ForwardMsaa+allocateMsaa context = do+  logDebug "Allocating ForwardMsaa resources"+  (_rpKey, renderPass) <- allocateRenderPassMsaa context+  (refcounted, color, depth, framebuffers) <- allocateFramebufferMsaa context renderPass++  pure ForwardMsaa+    { fmRenderPass   = renderPass+    , fmRenderArea   = fullSurface+    , fmClear        = clear+    , fmColor        = color+    , fmDepth        = depth+    , fmFrameBuffers = framebuffers+    , fmRelease      = refcounted+    }+  where+    fullSurface = Vk.Rect2D+      { Vk.offset = zero+      , Vk.extent = getSurfaceExtent context+      }+    clear = Vector.fromList+      [ clearColor+      , Vk.DepthStencil (Vk.ClearDepthStencilValue 1.0 0)+      , clearColor+      ]+    clearColor = Vk.Color zero++updateMsaa+  :: ( Resource.MonadResource m+     , MonadVulkan env m+     , HasLogFunc env+     , HasSwapchain context+     )+  => context+  -> ForwardMsaa+  -> m ForwardMsaa+updateMsaa context old@ForwardMsaa{fmRelease, fmRenderPass} = do+  releaseRefCounted fmRelease+  (refcounted, color, depth, framebuffers) <- allocateFramebufferMsaa context fmRenderPass+  pure old+    { fmColor        = color+    , fmDepth        = depth+    , fmFrameBuffers = framebuffers+    , fmRelease      = refcounted+    , fmRenderArea   = fullSurface+    }+  where+    fullSurface = Vk.Rect2D+      { Vk.offset = zero+      , Vk.extent = getSurfaceExtent context+      }++-- ** Render pass++allocateRenderPassMsaa+  :: ( MonadVulkan env m+     , Resource.MonadResource m+     , HasSwapchain context+     )+  => context+  -> m (Resource.ReleaseKey, Vk.RenderPass)+allocateRenderPassMsaa context = do+  device <- asks getDevice+  let+    format = getSurfaceFormat context+    depthFormat = getDepthFormat context+    msaa = getMultisample context++    attachments =+      [ color format msaa+      , depth depthFormat msaa+      , colorResolve format+      ]++  res@(_key, object) <- Vk.withRenderPass device (createInfo attachments) Nothing Resource.allocate+  Debug.nameObject device object "ForwardMSAA"+  pure res+  where+    createInfo attachments = zero+      { Vk.attachments  = Vector.fromList attachments+      , Vk.subpasses    = Vector.fromList [subpass]+      , Vk.dependencies = Vector.fromList [colorDeps, depthDeps]+      }++    color format msaa = zero+      { Vk.format         = format+      , Vk.samples        = msaa+      , Vk.finalLayout    = Vk.IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL+      , Vk.loadOp         = Vk.ATTACHMENT_LOAD_OP_CLEAR+      , Vk.storeOp        = Vk.ATTACHMENT_STORE_OP_DONT_CARE+      , Vk.stencilLoadOp  = Vk.ATTACHMENT_LOAD_OP_DONT_CARE+      , Vk.stencilStoreOp = Vk.ATTACHMENT_STORE_OP_DONT_CARE+      , Vk.initialLayout  = Vk.IMAGE_LAYOUT_UNDEFINED+      }++    depth format msaa = zero+      { Vk.format         = format+      , Vk.samples        = msaa+      , Vk.loadOp         = Vk.ATTACHMENT_LOAD_OP_CLEAR+      , Vk.storeOp        = Vk.ATTACHMENT_STORE_OP_DONT_CARE+      , Vk.stencilLoadOp  = Vk.ATTACHMENT_LOAD_OP_DONT_CARE+      , Vk.stencilStoreOp = Vk.ATTACHMENT_STORE_OP_DONT_CARE+      , Vk.initialLayout  = Vk.IMAGE_LAYOUT_UNDEFINED+      , Vk.finalLayout    = Vk.IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL+      }++    colorResolve format = zero+      { Vk.format      = format+      , Vk.samples     = Vk.SAMPLE_COUNT_1_BIT+      , Vk.finalLayout = Vk.IMAGE_LAYOUT_PRESENT_SRC_KHR+      , Vk.loadOp      = Vk.ATTACHMENT_LOAD_OP_DONT_CARE+      }++    subpass = zero+      { Vk.pipelineBindPoint = Vk.PIPELINE_BIND_POINT_GRAPHICS+      , Vk.colorAttachments = Vector.singleton zero+          { Vk.attachment = 0+          , Vk.layout     = Vk.IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL+          }+      , Vk.depthStencilAttachment = Just zero+          { Vk.attachment = 1+          , Vk.layout     = Vk.IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL+          }+      , Vk.resolveAttachments = Vector.singleton zero+          { Vk.attachment = 2+          , Vk.layout     = Vk.IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL+          }+      }++    colorDeps = zero+      { Vk.srcSubpass    = Vk.SUBPASS_EXTERNAL+      , Vk.dstSubpass    = 0+      , Vk.srcStageMask  = colorOut+      , Vk.srcAccessMask = zero+      , Vk.dstStageMask  = colorOut+      , Vk.dstAccessMask = colorRW+      }+      where+        colorOut =+          Vk.PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT++        colorRW =+          Vk.ACCESS_COLOR_ATTACHMENT_READ_BIT .|.+          Vk.ACCESS_COLOR_ATTACHMENT_WRITE_BIT++    depthDeps = zero+      { Vk.srcSubpass    = Vk.SUBPASS_EXTERNAL+      , Vk.dstSubpass    = 0+      , Vk.srcStageMask  = fragTests+      , Vk.srcAccessMask = depthW+      , Vk.dstStageMask  = fragTests+      , Vk.dstAccessMask = depthRW+      }+      where+        fragTests =+          Vk.PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT .|.+          Vk.PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT++        depthW =+          Vk.ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT++        depthRW =+          Vk.ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT .|.+          depthW++-- ** Framebuffer++type FramebuffersMsaa =+  ( RefCounted+  , Image.AllocatedImage+  , Image.AllocatedImage+  , Vector Vk.Framebuffer+  )++allocateFramebufferMsaa+  :: ( Resource.MonadResource m+     , MonadVulkan env m+     , HasLogFunc env+     , HasSwapchain context+     )+  => context+  -> Vk.RenderPass+  -> m FramebuffersMsaa+allocateFramebufferMsaa context renderPass = do+  device <- asks getDevice+  context' <- asks (, context)+  let extent@Vk.Extent2D{width, height} = getSurfaceExtent context++  (colorKey, color) <- Resource.allocate+    (Image.createColorResource context' extent)+    (Image.destroyAllocatedImage context')++  (depthKey, depth) <- Resource.allocate+    (Image.createDepthResource context' extent Nothing)+    (Image.destroyAllocatedImage context')++  perView <- for (getSwapchainViews context) \colorResolve -> do+    let+      attachments = Vector.fromList+        [ Image.aiImageView color+        , Image.aiImageView depth+        , colorResolve+        ]++      fbCI = zero+        { Vk.renderPass  = renderPass+        , Vk.width       = width+        , Vk.height      = height+        , Vk.attachments = attachments+        , Vk.layers      = 1+        }++    Vk.withFramebuffer device fbCI Nothing Resource.allocate++  let (framebufferKeys, framebuffers) = Vector.unzip perView+  releaseDebug <- toIO $ logDebug "Releasing ForwardMsaa resources"+  release <- newRefCounted do+    releaseDebug+    Resource.release colorKey+    Resource.release depthKey+    traverse_ Resource.release framebufferKeys++  pure (release, color, depth, framebuffers)++usePass :: (MonadIO io, HasRenderPass a) => a -> Word32 -> Vk.CommandBuffer -> io r -> io r+usePass render imageIndex cb =+  Vk.cmdUseRenderPass+    cb+    (beginInfo render imageIndex)+    Vk.SUBPASS_CONTENTS_INLINE++beginInfo :: HasRenderPass a => a -> Word32 -> Vk.RenderPassBeginInfo '[]+beginInfo rp imageIndex = zero+  { Vk.renderPass  = getRenderPass rp+  , Vk.framebuffer = getFramebuffers rp ! fromIntegral imageIndex+  , Vk.renderArea  = getRenderArea rp+  , Vk.clearValues = getClearValues rp+  }
+ src/Render/Lit/Colored/Model.hs view
@@ -0,0 +1,54 @@+module Render.Lit.Colored.Model+  ( Model++  , VertexAttrs(..)+  , vkVertexAttrs++  , InstanceAttrs+  ) where++import RIO++import Foreign (Storable(..))+import Geomancy (Transform, Vec2, Vec4)+import Geomancy.Vec3 qualified as Vec3+import Resource.Model qualified as Model+import Vulkan.Core10 qualified as Vk++type Model buf = Model.Indexed buf Vec3.Packed VertexAttrs++data VertexAttrs = VertexAttrs+  { vaBaseColor         :: Vec4+  , vaEmissiveColor     :: Vec4 -- XXX: a: alpha cutoff+  , vaMetallicRoughness :: Vec2+  , vaNormal            :: Vec3.Packed+  }+  deriving (Eq, Ord, Show, Generic)++instance Storable VertexAttrs where+  alignment ~_ = 4++  sizeOf ~_ = 16 + 16 + 8 + 12++  peek ptr = do+    vaBaseColor         <- peekByteOff ptr  0+    vaEmissiveColor     <- peekByteOff ptr 16+    vaMetallicRoughness <- peekByteOff ptr 32+    vaNormal            <- peekByteOff ptr 40+    pure VertexAttrs{..}++  poke ptr VertexAttrs{..} = do+    pokeByteOff ptr  0 vaBaseColor+    pokeByteOff ptr 16 vaEmissiveColor+    pokeByteOff ptr 32 vaMetallicRoughness+    pokeByteOff ptr 40 vaNormal++type InstanceAttrs = Transform++vkVertexAttrs :: [Vk.Format]+vkVertexAttrs =+  [ Vk.FORMAT_R32G32B32A32_SFLOAT -- vBaseColor         :: vec4+  , Vk.FORMAT_R32G32B32A32_SFLOAT -- vEmissiveColor     :: vec4+  , Vk.FORMAT_R32G32_SFLOAT       -- vMetallicRoughness :: vec2+  , Vk.FORMAT_R32G32B32_SFLOAT    -- vNormal            :: vec3+  ]
+ src/Render/Lit/Colored/Pipeline.hs view
@@ -0,0 +1,162 @@+module Render.Lit.Colored.Pipeline+  ( Pipeline+  , allocate+  , allocateBlend+  ) where++import RIO++import Control.Monad.Trans.Resource (ResourceT)+import Data.Tagged (Tagged(..))+import Vulkan.Core10 qualified as Vk+import Vulkan.Zero (zero)++import Engine.Vulkan.Pipeline qualified as Pipeline+import Engine.Vulkan.Types (HasVulkan, HasRenderPass(..), DsBindings)+import Render.Code (compileVert, compileFrag, glsl)+import Render.Code.Lit (litMain, structLight, shadowFuns, brdfSpecular)+import Render.DescSets.Set0 (Scene, vertexPos, instanceTransform)+import Render.DescSets.Set0.Code (set0binding0, set0binding1, set0binding2, set0binding3, set0binding4, set0binding5)+import Render.Lit.Colored.Model qualified as Model++type Config = Pipeline.Config '[Scene] Model.VertexAttrs Model.InstanceAttrs+type Pipeline = Pipeline.Pipeline '[Scene] Model.VertexAttrs Model.InstanceAttrs++allocate+  :: ( HasVulkan env+     , HasRenderPass renderpass+     )+  => Vk.SampleCountFlagBits+  -> Tagged Scene DsBindings+  -> renderpass+  -> ResourceT (RIO env) Pipeline+allocate multisample tset0 rp = do+  (_, p) <- Pipeline.allocate+    Nothing+    multisample+    (config tset0)+    rp+  pure p++allocateBlend+  :: ( HasVulkan env+     , HasRenderPass renderpass+     )+  => Vk.SampleCountFlagBits+  -> Tagged Scene DsBindings+  -> renderpass+  -> ResourceT (RIO env) Pipeline+allocateBlend multisample tset0 rp = do+  (_, p) <- Pipeline.allocate+    Nothing+    multisample+    (configBlend tset0)+    rp+  pure p++config :: Tagged Scene DsBindings -> Config+config (Tagged set0) = zero+  { Pipeline.cDescLayouts  = Tagged @'[Scene] [set0]+  , Pipeline.cVertexCode   = Just vertCode+  , Pipeline.cVertexInput  = vertexInput+  , Pipeline.cFragmentCode = Just fragCode+  }+  where+    vertexInput = Pipeline.vertexInput+      [ vertexPos+      , (Vk.VERTEX_INPUT_RATE_VERTEX, Model.vkVertexAttrs)+      , instanceTransform+      ]++configBlend :: Tagged Scene DsBindings -> Config+configBlend tset0 = (config tset0)+  { Pipeline.cBlend = True+  }++vertCode :: ByteString+vertCode =+  $(compileVert [glsl|+    #version 450+    #extension GL_ARB_separate_shader_objects : enable++    ${set0binding0}++    layout(location = 0) in vec3 vPosition;+    layout(location = 1) in vec4 vBaseColor;+    layout(location = 2) in vec4 vEmissiveColor;+    layout(location = 3) in vec2 vMetallicRoughness;+    layout(location = 4) in vec3 vNormal;++    layout(location = 5) in mat4 iModel;++    layout(location = 0) out vec4 fPosition;+    layout(location = 1) out vec4 fColor;+    layout(location = 2) out vec4 fEmissiveColor;+    layout(location = 3) out vec2 fMetallicRoughness;+    layout(location = 4) out vec3 fNormal;++    void main() {+      fPosition = iModel * vec4(vPosition, 1.0);++      gl_Position+        = scene.projection+        * scene.view+        * fPosition;++      fNormal = transpose(mat3(inverse(iModel))) * vNormal; // TODO: use modelInv++      fColor = vBaseColor;+      fColor.rgb *= vBaseColor.a;++      fEmissiveColor = vEmissiveColor;+      fMetallicRoughness = vMetallicRoughness;+    }+  |])++fragCode :: ByteString+fragCode =+  $(compileFrag [glsl|+    #version 450+    #extension GL_ARB_separate_shader_objects : enable+    #extension GL_EXT_nonuniform_qualifier : enable++    // TODO: move to spec constant+    const uint MAX_LIGHTS = 255;+    const float PCF_STEP = 1.5 / 4096;++    // TODO: move to material+    const float reflectivity = 1.0/256.0;++    ${structLight}++    ${set0binding0}+    ${set0binding1}+    ${set0binding2}+    ${set0binding3}+    ${set0binding4}+    ${set0binding5}++    layout(location = 0) in vec4 fPosition;+    layout(location = 1) in vec4 fColor;+    layout(location = 2) in vec4 fEmissiveColor;+    layout(location = 3) in vec2 fMetallicRoughness;+    layout(location = 4) in vec3 fNormal;++    layout(location = 0) out vec4 oColor;++    ${shadowFuns}+    ${brdfSpecular}++    void main() {+      vec4 baseColor = fColor; // XXX: assuming premultiplied alpha+      float metallic = fMetallicRoughness[0];+      float roughness = fMetallicRoughness[1];+      float occlusion = 1.0 - 0.0;++      vec3 normal = normalize(fNormal);++      ${litMain}++      oColor.rgb += pow(fEmissiveColor.rgb, vec3(2.2));+    }+  |])
+ src/Render/Lit/Material.hs view
@@ -0,0 +1,74 @@+{-# OPTIONS_GHC -fplugin Foreign.Storable.Generic.Plugin #-}++module Render.Lit.Material+  ( Material(..)+  , shiftTextures+  ) where++import RIO++import Foreign.Storable.Generic (GStorable)+import Geomancy (Vec2, Vec4, vec2, vec4)+import Vulkan.Zero (Zero(..))++data Material = Material+  { mBaseColor         :: Vec4+  , mMetallicRoughness :: Vec2+  , mEmissive          :: Vec4+  , mNormalScale       :: Float+  , mAlphaCutoff       :: Float++  , mBaseColorTex         :: Int32+  , mMetallicRoughnessTex :: Int32+  , mEmissiveTex          :: Int32+  , mNormalTex            :: Int32+  , mAmbientOcclusionTex  :: Int32+  }+  deriving (Eq, Ord, Show, Generic)++instance GStorable Material++instance Zero Material where+  zero = Material+    { mBaseColor         = vec4 1.0 1.0 1.0 1.0+    , mMetallicRoughness = vec2 0 0.6+    , mEmissive          = vec4 0 0 0 0+    , mNormalScale       = 1.0+    , mAlphaCutoff       = 0.5++    , mBaseColorTex         = -1+    , mMetallicRoughnessTex = -1+    , mNormalTex            = -1+    , mEmissiveTex          = -1+    , mAmbientOcclusionTex  = -1+    }++shiftTextures :: Int32 -> Material -> Material+shiftTextures offset Material{..} = Material+  { mBaseColorTex =+      if mBaseColorTex > -1 then+        mBaseColorTex + offset+      else+        mBaseColorTex+  , mMetallicRoughnessTex =+      if mMetallicRoughnessTex > -1 then+        mMetallicRoughnessTex + offset+      else+        mMetallicRoughnessTex+  , mEmissiveTex =+      if mEmissiveTex > -1 then+        mEmissiveTex + offset+      else+        mEmissiveTex+  , mNormalTex =+      if mNormalTex > -1 then+        mNormalTex + offset+      else+        mNormalTex+  , mAmbientOcclusionTex =+      if mAmbientOcclusionTex > -1 then+        mAmbientOcclusionTex + offset+      else+        mAmbientOcclusionTex+  , ..+  }
+ src/Render/Lit/Material/Collect.hs view
@@ -0,0 +1,81 @@+module Render.Lit.Material.Collect+  ( LoadedModel+  , SceneModel(..)+  , sceneMaterials+  , modelMaterials+  , nodeMaterials+  ) where++import RIO++import RIO.Map qualified as Map+import RIO.Vector.Storable qualified as Storable++import Render.Lit.Material (shiftTextures)+import Render.Lit.Material.Model qualified as LitMaterial+import Resource.Buffer qualified as Buffer+import Resource.Mesh.Types qualified as Mesh+import Resource.Mesh.Lit qualified as Lit+import Render.Lit.Material (Material)++type LoadedModel =+  ( Mesh.Meta+  , Storable.Vector Lit.MaterialNode+  , LitMaterial.Model 'Buffer.Staged+  )++data SceneModel models textures = SceneModel+  { smLabel            :: Text+  , smGetModel         :: models -> LoadedModel+  , smGetTextureOffset :: textures -> Int32+  }++sceneMaterials+  :: Foldable t+  => models+  -> textures+  -> t (SceneModel models textures)+  -> Storable.Vector Material+sceneMaterials loadedModels combinedTextures =+  Storable.fromList . Map.elems . foldl' beep mempty+  where+    beep acc SceneModel{..} =+      let+        (_meta, materialNodes, _model) = smGetModel loadedModels+        textureOffset = smGetTextureOffset combinedTextures+      in+        modelMaterials smLabel textureOffset acc (Storable.toList materialNodes)++modelMaterials+  :: Foldable t+  => Text+  -> Int32+  -> Map Int Material+  -> t Lit.MaterialNode+  -> Map Int Material+modelMaterials label textureOffset =+  foldr (nodeMaterials label textureOffset)++nodeMaterials+  :: Text+  -> Int32+  -> Lit.MaterialNode+  -> Map Int Material+  -> Map Int Material+nodeMaterials label textureStart Lit.MaterialNode{mnMaterialIx, mnMaterial} collection =+  case Map.lookup mnMaterialIx collection of+    Nothing ->+      Map.insert mnMaterialIx newMaterial collection+    Just oldMaterial ->+      if newMaterial /= oldMaterial then+        error $ unlines+          [ "Ouf... The material indices are clashing for " <> show label+          , "At: " <> show mnMaterialIx+          , "Old: " <> show oldMaterial+          , "Now: " <> show mnMaterial+          ]+      else+        collection+  where+    newMaterial =+      shiftTextures textureStart mnMaterial
+ src/Render/Lit/Material/Model.hs view
@@ -0,0 +1,69 @@+{-# OPTIONS_GHC -fplugin Foreign.Storable.Generic.Plugin #-}++module Render.Lit.Material.Model+  ( Model+  , VertexAttrs(..)+  , vkVertexAttrs++  , InstanceAttrs+  -- , InstanceBuffers(..)++  -- , TextureParams(..)+  -- , vkInstanceTexture++  -- , allocateInstancesWith+  , Transform+  , Material+  ) where++import RIO++import Foreign (Storable(..))+import Geomancy (Transform, Vec2)+import Geomancy.Vec3 qualified as Vec3+import Vulkan.Core10 qualified as Vk++import Resource.Model qualified as Model+import Render.Lit.Material (Material)++type Model buf = Model.Indexed buf Vec3.Packed VertexAttrs++data VertexAttrs = VertexAttrs+  { vaTexCoord0 :: Vec2+  , vaTexCoord1 :: Vec2+  , vaNormal    :: Vec3.Packed+  , vaTangent   :: Vec3.Packed+  , vaMaterial  :: Word32+  }+  deriving (Eq, Ord, Show, Generic)++instance Storable VertexAttrs where+  alignment ~_ = 4++  sizeOf ~_ = 8 + 8 + 12 + 12 + 4++  peek ptr = do+    vaTexCoord0 <- peekByteOff ptr 0+    vaTexCoord1 <- peekByteOff ptr 8+    vaNormal    <- peekByteOff ptr 16+    vaTangent   <- peekByteOff ptr 28+    vaMaterial  <- peekByteOff ptr 40+    pure VertexAttrs{..}++  poke ptr VertexAttrs{..} = do+    pokeByteOff ptr  0 vaTexCoord0+    pokeByteOff ptr  8 vaTexCoord1+    pokeByteOff ptr 16 vaNormal+    pokeByteOff ptr 28 vaTangent+    pokeByteOff ptr 40 vaMaterial++vkVertexAttrs :: [Vk.Format]+vkVertexAttrs =+  [ Vk.FORMAT_R32G32_SFLOAT    -- vTexCoord0 :: vec2+  , Vk.FORMAT_R32G32_SFLOAT    -- vTexCoord1 :: vec2+  , Vk.FORMAT_R32G32B32_SFLOAT -- vNormal    :: vec3+  , Vk.FORMAT_R32G32B32_SFLOAT -- vTangent   :: vec3+  , Vk.FORMAT_R32_UINT         -- vMaterial  :: uint+  ]++type InstanceAttrs = Transform
+ src/Render/Lit/Material/Pipeline.hs view
@@ -0,0 +1,237 @@+module Render.Lit.Material.Pipeline+  ( Pipeline+  , allocate+  , allocateBlend+  ) where++import RIO++import Control.Monad.Trans.Resource (ResourceT)+import Data.Tagged (Tagged(..))+import Vulkan.Core10 qualified as Vk+import Vulkan.Zero (zero)++import Engine.Vulkan.Pipeline qualified as Pipeline+import Engine.Vulkan.Types (HasVulkan, HasRenderPass(..), DsBindings)+import Render.Code (compileVert, compileFrag, glsl)+import Render.DescSets.Set0 (Scene, vertexPos, instanceTransform)+import Render.DescSets.Set0.Code (set0binding0, set0binding1, set0binding2, set0binding3, set0binding4, set0binding5, set0binding6)+import Render.Code.Lit (litMain, shadowFuns, structLight, structMaterial, brdfSpecular)+import Render.Lit.Material.Model qualified as Model++type Config = Pipeline.Config '[Scene] Model.VertexAttrs Model.InstanceAttrs+type Pipeline = Pipeline.Pipeline '[Scene] Model.VertexAttrs Model.InstanceAttrs++allocate+  :: ( HasVulkan env+     , HasRenderPass renderpass+     )+  => Vk.SampleCountFlagBits+  -> Tagged Scene DsBindings+  -> renderpass+  -> ResourceT (RIO env) Pipeline+allocate multisample tset0 rp = do+  (_, p) <- Pipeline.allocate+    Nothing+    multisample+    (config tset0)+    rp+  pure p++allocateBlend+  :: ( HasVulkan env+     , HasRenderPass renderpass+     )+  => Vk.SampleCountFlagBits+  -> Tagged Scene DsBindings+  -> renderpass+  -> ResourceT (RIO env) Pipeline+allocateBlend multisample tset0 rp = do+  (_, p) <- Pipeline.allocate+    Nothing+    multisample+    (configBlend tset0)+    rp+  pure p++config :: Tagged Scene DsBindings -> Config+config (Tagged set0) = zero+  { Pipeline.cDescLayouts  = Tagged @'[Scene] [set0]+  , Pipeline.cVertexCode   = Just vertCode+  , Pipeline.cVertexInput  = vertexInput+  , Pipeline.cFragmentCode = Just fragCode+  }+  where+    vertexInput = Pipeline.vertexInput+      [ vertexPos -- vPosition+      , (Vk.VERTEX_INPUT_RATE_VERTEX, Model.vkVertexAttrs)+      , instanceTransform+      ]++configBlend :: Tagged Scene DsBindings -> Config+configBlend tset0 = (config tset0)+  { Pipeline.cBlend      = True+  , Pipeline.cDepthWrite = False+  }++vertCode :: ByteString+vertCode =+  $(compileVert [glsl|+    #version 450+    #extension GL_ARB_separate_shader_objects : enable++    ${set0binding0}++    // vertexPos+    layout(location = 0) in vec3 vPosition;+    // vertexAttrs+    layout(location = 1) in vec2 vTexCoord0;+    layout(location = 2) in vec2 vTexCoord1;+    layout(location = 3) in vec3 vNormal;+    layout(location = 4) in vec3 vTangent;+    layout(location = 5) in uint vMaterial;++    // transformMat+    layout(location = 6) in mat4 iModel;++    layout(location = 0)      out  vec4 fPosition;+    layout(location = 1)      out  vec2 fTexCoord0;+    layout(location = 2)      out  vec2 fTexCoord1;+    layout(location = 3) flat out  uint fMaterial;+    layout(location = 4)      out  mat3 fTBN;++    void main() {+      fPosition = iModel * vec4(vPosition, 1.0);++      gl_Position+        = scene.projection+        * scene.view+        * fPosition;++      fTexCoord0 = vTexCoord0;+      fTexCoord1 = vTexCoord1;++      vec3 t = normalize(vec3(iModel * vec4(vTangent, 0.0)));+      vec3 n = normalize(vec3(iModel * vec4(vNormal, 0.0)));+      vec3 to = normalize(t - dot(t, n) * n); // re-orthogonalize T with respect to N+      fTBN = mat3(to, cross(n, to), n);++      fMaterial = vMaterial;+    }+  |])++fragCode :: ByteString+fragCode =+  $(compileFrag [glsl|+    #version 450+    #extension GL_ARB_separate_shader_objects : enable+    #extension GL_EXT_nonuniform_qualifier : enable++    // XXX: copypasta from Lit.Colored+    // TODO: move to spec constant+    const uint MAX_LIGHTS = 255;+    const float PCF_STEP = 1.5 / 4096;++    const uint MAX_MATERIALS = 2048;++    ${structLight}+    ${structMaterial}++    ${set0binding0}+    ${set0binding1}+    ${set0binding2}+    ${set0binding3}+    ${set0binding4} // lights+    ${set0binding5} // shadowmap+    ${set0binding6} // materials++    layout(location = 0)      in vec4 fPosition;+    layout(location = 1)      in vec2 fTexCoord0;+    layout(location = 2)      in vec2 fTexCoord1;+    layout(location = 3) flat in uint fMaterial;+    layout(location = 4)      in mat3 fTBN;++    layout(location = 0) out vec4 oColor;++    ${shadowFuns}+    ${brdfSpecular}++    void main() {+      Material material = materials[fMaterial];+      vec4 baseColor = material.baseColor;+      float metallic = material.metallicRoughness[0];+      float roughness = material.metallicRoughness[1];+      float occlusion = 0;+      vec4 emissive = material.emissive;++      if (material.baseColorTex > -1) {+        baseColor *= texture(+          sampler2D(+            textures[nonuniformEXT(material.baseColorTex)],+            samplers[0]+          ),+          fTexCoord0+        );+      }++      if (baseColor.a < material.alphaCutoff) {+        discard;+      }++      baseColor.rgb *= baseColor.a;++      if (material.metallicRoughnessTex > -1) {+        vec3 packed = texture(+          sampler2D(+            textures[nonuniformEXT(material.metallicRoughnessTex)],+            samplers[0]+          ),+          fTexCoord0+        ).rgb;+        packed = pow(packed, vec3(1.0/2.2));+        occlusion = packed.r;+        metallic *= packed.b;+        roughness *= packed.g;+      }++      // // TODO: combine with MR as channel R.+      // occlusion = texture(+      //   sampler2D(+      //     textures[nonuniformEXT(max(0, material.ambientOcclusionTex))],+      //     samplers[0]+      //   ),+      //   fTexCoord0+      // ).r;+      // // occlusion = pow(occlusion, 1.0/2.2);++      if (material.emissiveTex > -1) {+        emissive *= texture(+          sampler2D(+            textures[nonuniformEXT(material.emissiveTex)],+            samplers[0]+          ),+          fTexCoord0+        );+      }++      vec3 normal = fTBN[2];+      if (material.normalTex > -1) {+        vec3 normalsColor = texture(+          sampler2D(+            textures[nonuniformEXT(material.normalTex)],+            samplers[0]+          ),+          fTexCoord0+        ).rgb;++        // XXX: convert normal non-colors to linear values from sRGB texture colorspace+        vec3 normals = pow(normalsColor, vec3(1.0/2.2)) * 2.0 - 1.0;++        normal = normalize(fTBN * normals);+      }++      ${litMain}++      oColor.rgb += pow(emissive.rgb, vec3(2.2));+    }+  |])
+ src/Render/Lit/Textured/Model.hs view
@@ -0,0 +1,162 @@+module Render.Lit.Textured.Model+  ( Model+  , VertexAttrs(..)+  , vkVertexAttrs++  , InstanceAttrs(..)+  , InstanceBuffers(..)++  , TextureParams(..)+  , vkInstanceTexture++  , allocateInstancesWith+  , Transform+  ) where++import RIO+++import Foreign (Storable(..))+import Geomancy (Transform, Vec2, Vec4)+import Geomancy.Vec3 qualified as Vec3+import RIO.Vector.Storable qualified as VectorS+import UnliftIO.Resource (MonadResource, ReleaseKey, allocate)+import Vulkan.Core10 qualified as Vk+import Vulkan.Zero (Zero(..))++import Resource.Buffer qualified as Buffer+import Resource.Model qualified as Model++type Model buf = Model.Indexed buf Vec3.Packed VertexAttrs++data VertexAttrs = VertexAttrs+  { vaTexCoord :: Vec2+  , vaNormal   :: Vec3.Packed+  }+  deriving (Eq, Ord, Show, Generic)++instance Storable VertexAttrs where+  alignment ~_ = 4++  sizeOf ~_ = 8 + 12++  peek ptr = do+    vaTexCoord <- peekByteOff ptr 0+    vaNormal   <- peekByteOff ptr 8+    pure VertexAttrs{..}++  poke ptr VertexAttrs{..} = do+    pokeByteOff ptr 0 vaTexCoord+    pokeByteOff ptr 8 vaNormal++vkVertexAttrs :: [Vk.Format]+vkVertexAttrs =+  [ Vk.FORMAT_R32G32_SFLOAT    -- vTexCoord :: vec2+  , Vk.FORMAT_R32G32B32_SFLOAT -- vNormal   :: vec3+  ]++data InstanceAttrs = InstanceAttrs+  { textureParams :: TextureParams+  , transformMat4 :: Transform+  }++instance Zero InstanceAttrs where+  zero = InstanceAttrs+    { textureParams = zero+    , transformMat4 = mempty+    }++data InstanceBuffers textureStage transformStage = InstanceBuffers+  { ibTexture   :: InstanceTexture textureStage+  , ibTransform :: InstanceTransform transformStage+  }++type InstanceTexture stage = Buffer.Allocated stage TextureParams++type InstanceTransform stage = Buffer.Allocated stage Transform++instance Model.HasVertexBuffers (InstanceBuffers textureStage transformStage) where+  type VertexBuffersOf (InstanceBuffers textureStage transformStage) = InstanceAttrs++  {-# INLINE getVertexBuffers #-}+  getVertexBuffers InstanceBuffers{..} =+    [ Buffer.aBuffer ibTexture+    , Buffer.aBuffer ibTransform+    ]++  {-# INLINE getInstanceCount #-}+  getInstanceCount InstanceBuffers{..} =+    min+      (Buffer.aUsed ibTexture)+      (Buffer.aUsed ibTransform)++data TextureParams = TextureParams+  { tpScale     :: Vec2+  , tpOffset    :: Vec2+  , tpGamma     :: Vec4+  , tpSamplerId :: Int32+  , tpTextureId :: Int32+  }+  deriving (Show)++instance Zero TextureParams where+  zero = TextureParams+    { tpScale     = 1+    , tpOffset    = 0+    , tpGamma     = 1.0+    , tpSamplerId = minBound+    , tpTextureId = minBound+    }++instance Storable TextureParams where+  alignment ~_ = 8++  sizeOf ~_ = 8 + 8 + 16 + 4 + 4++  poke ptr TextureParams{..} = do+    pokeByteOff ptr  0 tpScale+    pokeByteOff ptr  8 tpOffset+    pokeByteOff ptr 16 tpGamma+    pokeByteOff ptr 32 tpSamplerId+    pokeByteOff ptr 36 tpTextureId++  peek ptr = do+    tpScale     <- peekByteOff ptr  0+    tpOffset    <- peekByteOff ptr  8+    tpGamma     <- peekByteOff ptr 16+    tpSamplerId <- peekByteOff ptr 32+    tpTextureId <- peekByteOff ptr 36+    pure TextureParams{..}++vkInstanceTexture :: [Vk.Format]+vkInstanceTexture =+  [ Vk.FORMAT_R32G32B32A32_SFLOAT -- iTextureScaleOffset :: vec4+  , Vk.FORMAT_R32G32B32A32_SFLOAT -- iTextureGamma       :: vec4+  , Vk.FORMAT_R32G32_SINT         -- iTextureIds         :: ivec2+  ]++allocateInstancesWith+  :: ( MonadResource m+     , MonadUnliftIO m+     )+  => (Vk.BufferUsageFlagBits -> Int -> VectorS.Vector TextureParams -> m (InstanceTexture texture))+  -> (Vk.BufferUsageFlagBits -> Int -> VectorS.Vector Transform -> m (InstanceTransform transform))+  -> (forall stage a . Buffer.Allocated stage a -> m ())+  -> [InstanceAttrs]+  -> m (ReleaseKey, InstanceBuffers texture transform)+allocateInstancesWith createTextures createTransforms bufferDestroy instances = do+  ul <- askUnliftIO+  allocate (create ul) (destroy ul)+  where+    textures   = VectorS.fromList $ map textureParams instances+    transforms = VectorS.fromList $ map transformMat4 instances+    numInstances = VectorS.length textures++    create (UnliftIO ul) = ul do+      ibTexture   <- createTextures   Vk.BUFFER_USAGE_VERTEX_BUFFER_BIT numInstances textures+      ibTransform <- createTransforms Vk.BUFFER_USAGE_VERTEX_BUFFER_BIT numInstances transforms+      pure InstanceBuffers{..}++    destroy (UnliftIO ul) InstanceBuffers{..} = ul do+      bufferDestroy ibTexture+      bufferDestroy ibTransform
+ src/Render/Lit/Textured/Pipeline.hs view
@@ -0,0 +1,184 @@+module Render.Lit.Textured.Pipeline+  ( Pipeline+  , allocate+  , allocateBlend+  ) where++import RIO++import Control.Monad.Trans.Resource (ResourceT)+import Data.Tagged (Tagged(..))+import Vulkan.Core10 qualified as Vk+import Vulkan.Zero (zero)++import Engine.Vulkan.Pipeline qualified as Pipeline+import Engine.Vulkan.Types (HasVulkan, HasRenderPass(..), DsBindings)+import Render.Code (compileVert, compileFrag, glsl)+import Render.DescSets.Set0 (Scene, vertexPos, instanceTransform)+import Render.DescSets.Set0.Code (set0binding0, set0binding1, set0binding2, set0binding3, set0binding4, set0binding5)+import Render.Code.Lit (litMain, shadowFuns, structLight, brdfSpecular)+import Render.Lit.Textured.Model qualified as Model++type Config = Pipeline.Config '[Scene] Model.VertexAttrs Model.InstanceAttrs+type Pipeline = Pipeline.Pipeline '[Scene] Model.VertexAttrs Model.InstanceAttrs++allocate+  :: ( HasVulkan env+     , HasRenderPass renderpass+     )+  => Vk.SampleCountFlagBits+  -> Tagged Scene DsBindings+  -> renderpass+  -> ResourceT (RIO env) Pipeline+allocate multisample tset0 rp = do+  (_, p) <- Pipeline.allocate+    Nothing+    multisample+    (config tset0)+    rp+  pure p++allocateBlend+  :: ( HasVulkan env+     , HasRenderPass renderpass+     )+  => Vk.SampleCountFlagBits+  -> Tagged Scene DsBindings+  -> renderpass+  -> ResourceT (RIO env) Pipeline+allocateBlend multisample tset0 rp = do+  (_, p) <- Pipeline.allocate+    Nothing+    multisample+    (configBlend tset0)+    rp+  pure p++config :: Tagged Scene DsBindings -> Config+config (Tagged set0) = zero+  { Pipeline.cDescLayouts  = Tagged @'[Scene] [set0]+  , Pipeline.cVertexCode   = Just vertCode+  , Pipeline.cVertexInput  = vertexInput+  , Pipeline.cFragmentCode = Just fragCode+  }+  where+    vertexInput = Pipeline.vertexInput+      [ vertexPos -- vPosition+      , (Vk.VERTEX_INPUT_RATE_VERTEX,   Model.vkVertexAttrs)+      , (Vk.VERTEX_INPUT_RATE_INSTANCE, Model.vkInstanceTexture)+      , instanceTransform+      ]++configBlend :: Tagged Scene DsBindings -> Config+configBlend tset0 = (config tset0)+  { Pipeline.cBlend      = True+  , Pipeline.cDepthWrite = False+  }++vertCode :: ByteString+vertCode =+  $(compileVert [glsl|+    #version 450+    #extension GL_ARB_separate_shader_objects : enable++    ${set0binding0}++    // vertexPos+    layout(location = 0) in vec3 vPosition;+    // vertexAttrs+    layout(location = 1) in vec2 vTexCoord;+    layout(location = 2) in vec3 vNormal;+    // textureParams+    layout(location = 3) in  vec4 iTextureScaleOffset;+    layout(location = 4) in  vec4 iTextureGamma;+    layout(location = 5) in ivec2 iTextureIds;++    // transformMat+    layout(location = 6) in mat4 iModel;++    layout(location = 0)      out  vec4 fPosition;+    layout(location = 1)      out  vec3 fNormal;+    layout(location = 2)      out  vec2 fTexCoord;+    layout(location = 3) flat out  vec4 fTextureGamma;+    layout(location = 4) flat out ivec2 fTextureIds;++    void main() {+      fPosition = iModel * vec4(vPosition, 1.0);++      gl_Position+        = scene.projection+        * scene.view+        * fPosition;++      fNormal = transpose(mat3(inverse(iModel))) * vNormal; // TODO: use modelInv++      fTexCoord     = vTexCoord * iTextureScaleOffset.st + iTextureScaleOffset.pq;+      fTextureGamma = iTextureGamma;+      fTextureIds   = iTextureIds;+    }+  |])++fragCode :: ByteString+fragCode =+  $(compileFrag [glsl|+    #version 450+    #extension GL_ARB_separate_shader_objects : enable+    #extension GL_EXT_nonuniform_qualifier : enable++    // XXX: copypasta from Lit.Colored+    // TODO: move to spec constant+    const uint MAX_LIGHTS = 255;+    const float PCF_STEP = 1.5 / 4096;++    // TODO: move to material+    const float reflectivity = 1.0/256.0;++    ${structLight}++    ${set0binding0}+    ${set0binding1}+    ${set0binding2}+    ${set0binding3}+    ${set0binding4}+    ${set0binding5}++    layout(location = 0)      in  vec4 fPosition;+    layout(location = 1)      in  vec3 fNormal;+    layout(location = 2)      in  vec2 fTexCoord;+    layout(location = 3) flat in  vec4 fTextureGamma;+    layout(location = 4) flat in ivec2 fTextureIds;++    layout(location = 0) out vec4 oColor;++    ${shadowFuns}+    ${brdfSpecular}++    void main() {+      // XXX: fall back to solid color+      vec4 baseColor = fTextureGamma;+      if ((fTextureIds.t > -1) && (fTextureIds.s > -1)) {+        vec4 texel = texture(+          sampler2D(+            textures[nonuniformEXT(fTextureIds.t)],+            samplers[nonuniformEXT(fTextureIds.s)]+          ),+          fTexCoord+        );++        if (texel.a < 0.5) discard;++        vec3 color = pow(texel.rgb, fTextureGamma.rgb);+        float combinedAlpha = texel.a * fTextureGamma.a;+        baseColor = vec4(color * combinedAlpha, combinedAlpha);+      }++      // TODO: get from textures+      float metallic = 0.0;+      float roughness = 0.6;+      float occlusion = 1.0 - 0.0;++      vec3 normal = normalize(fNormal);++      ${litMain}+    }+  |])
+ src/Render/ShadowMap/Pipeline.hs view
@@ -0,0 +1,90 @@+module Render.ShadowMap.Pipeline+  ( Pipeline+  , allocate+  , Settings(..)+  , defaults+  ) where++import RIO++import Control.Monad.Trans.Resource (ResourceT)+import Data.Tagged (Tagged(..))+import Geomancy (Transform)+import Vulkan.Core10 qualified as Vk+import Vulkan.Zero (zero)++import Engine.Vulkan.Pipeline qualified as Pipeline+import Engine.Vulkan.Types (HasVulkan, HasRenderPass(..), DsBindings)+import Render.Code (compileVert, glsl)+import Render.Code.Lit (structLight)+import Render.DescSets.Set0 (vertexPos, instanceTransform)+import Render.DescSets.Sun (Sun, pattern MAX_VIEWS)++type Config = Pipeline.Config '[Sun] () Transform+type Pipeline = Pipeline.Pipeline '[Sun] () Transform++data Settings = Settings+  { cull      :: Vk.CullModeFlagBits+  , depthBias :: Maybe (Float, Float)+  }++defaults :: Settings+defaults = Settings+  { cull      = Vk.CULL_MODE_BACK_BIT+  , depthBias = Just (2.0, 2.5)+  }++allocate+  :: ( HasVulkan env+     , HasRenderPass renderpass+     )+  => Tagged Sun DsBindings+  -> renderpass+  -> Settings+  -> ResourceT (RIO env) Pipeline+allocate tset0 rp settings = do+  (_, p) <- Pipeline.allocate+    Nothing+    Vk.SAMPLE_COUNT_1_BIT+    (config tset0 settings)+    rp+  pure p++config :: Tagged Sun DsBindings -> Settings -> Config+config (Tagged set0) Settings{..} = zero+  { Pipeline.cDescLayouts  = Tagged @'[Sun] [set0]+  , Pipeline.cVertexCode   = Just vertCode+  , Pipeline.cVertexInput  = vertexInput+  , Pipeline.cDepthBias    = depthBias+  , Pipeline.cCull         = cull+  }+  where+    vertexInput = Pipeline.vertexInput+      [ vertexPos+      , instanceTransform+      ]++vertCode :: ByteString+vertCode =+  $(compileVert [glsl|+    #version 450+    #extension GL_ARB_separate_shader_objects : enable+    #extension GL_EXT_multiview : enable++    ${structLight}++    layout(set=0, binding=0, std140) uniform Globals {+      Light lights[${MAX_VIEWS}];+    };++    layout(location = 0) in vec3 vPosition;++    layout(location = 1) in mat4 iModel;++    void main() {+      gl_Position+        = lights[gl_ViewIndex].viewProjection+        * iModel+        * vec4(vPosition, 1.0);+    }+  |])
+ src/Render/ShadowMap/RenderPass.hs view
@@ -0,0 +1,229 @@+{-# LANGUAGE OverloadedLists #-}++module Render.ShadowMap.RenderPass where++import RIO++import Control.Monad.Trans.Resource qualified as Resource+import Data.Vector qualified as Vector+import RIO.Vector.Partial ((!))+import Vulkan.Core10 qualified as Vk+import Vulkan.Core11.Promoted_From_VK_KHR_multiview qualified as Khr+import Vulkan.NamedType ((:::))+import Vulkan.Utils.Debug qualified as Debug+import Vulkan.Zero (zero)+import Vulkan.CStruct.Extends (pattern (:&), pattern (::&))++import Engine.Types.RefCounted (RefCounted, newRefCounted, resourceTRefCount)+import Engine.Vulkan.Types (HasVulkan(..), HasSwapchain(..), HasRenderPass(..), RenderPass(..), MonadVulkan)+import Resource.Image (AllocatedImage)+import Resource.Image qualified as Image++-- * Depth-only pass for shadowmapping pipelines++data ShadowMap = ShadowMap+  { smRenderPass  :: Vk.RenderPass+  , smDepthImage  :: AllocatedImage+  , smFrameBuffer :: Vk.Framebuffer+  , smRenderArea  :: Vk.Rect2D+  , smExtent      :: Vk.Extent2D+  , smLayerCount  :: Word32+  , smClear       :: Vector Vk.ClearValue+  , smRelease     :: RefCounted+  }++instance HasRenderPass ShadowMap where+  getRenderPass   = smRenderPass+  getFramebuffers = Vector.replicate 10 . smFrameBuffer+  getClearValues  = smClear+  getRenderArea   = smRenderArea++instance RenderPass ShadowMap where+  allocateRenderpass_ = error "Don't allocate ShadowMap via class"+  updateRenderpass    = error "Don't recreate ShadowMap on resize"+  refcountRenderpass  = resourceTRefCount . smRelease++allocate+  :: ( Resource.MonadResource m+     , MonadVulkan env m+     , HasLogFunc env+     , HasSwapchain context+     )+  => context+  -> Word32+  -> "light count" ::: Word32+  -> m ShadowMap+allocate context mapSize layerCount = do+  logDebug "Allocating ShadowMap resources"+  (_rpKey, renderPass) <- allocateRenderPass context (2 ^ layerCount - 1) 0+  (refcounted, depthImage, framebuffer) <- allocateFramebuffer context extent layerCount renderPass++  pure ShadowMap+    { smRenderPass  = renderPass+    , smExtent      = extent+    , smLayerCount  = layerCount+    , smRenderArea  = fullSurface+    , smClear       = clear+    , smDepthImage  = depthImage+    , smFrameBuffer = framebuffer+    , smRelease     = refcounted+    }+  where+    extent = Vk.Extent2D{width=mapSize, height=mapSize}++    fullSurface = Vk.Rect2D+      { Vk.offset = zero+      , Vk.extent = extent+      }++    clear = Vector.fromList+      [ Vk.DepthStencil (Vk.ClearDepthStencilValue 1.0 0)+      ]++-- ** Render pass++allocateRenderPass+  :: ( MonadVulkan env m+     , Resource.MonadResource m+     , HasSwapchain context+     )+  => context+  -> Word32+  -> Word32+  -> m (Resource.ReleaseKey, Vk.RenderPass)+allocateRenderPass context viewMask correlationMask = do+  device <- asks getDevice+  let depthFormat = getDepthFormat context++  res@(_key, object) <- Vk.withRenderPass device (createInfo depthFormat) Nothing Resource.allocate+  Debug.nameObject device object "ShadowMap.RP"+  pure res+  where+    createInfo depthFormat =+      zero+        { Vk.attachments  = Vector.fromList [depth depthFormat]+        , Vk.subpasses    = Vector.fromList [subpass]+        , Vk.dependencies = Vector.fromList [pre, post]+        }+      ::& Khr.RenderPassMultiviewCreateInfo+        { Khr.viewMasks        = [viewMask]+        , Khr.viewOffsets      = []+        , Khr.correlationMasks = [correlationMask]+        }+      :& ()++    depth depthFormat = zero+      { Vk.format         = depthFormat+      , Vk.samples        = Vk.SAMPLE_COUNT_1_BIT+      , Vk.loadOp         = Vk.ATTACHMENT_LOAD_OP_CLEAR+      , Vk.storeOp        = Vk.ATTACHMENT_STORE_OP_STORE+      , Vk.stencilLoadOp  = Vk.ATTACHMENT_LOAD_OP_DONT_CARE+      , Vk.stencilStoreOp = Vk.ATTACHMENT_STORE_OP_DONT_CARE+      , Vk.initialLayout  = Vk.IMAGE_LAYOUT_UNDEFINED+      , Vk.finalLayout    = Vk.IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL+      }++    subpass = zero+      { Vk.pipelineBindPoint = Vk.PIPELINE_BIND_POINT_GRAPHICS+      , Vk.depthStencilAttachment = Just zero+          { Vk.attachment = 0+          , Vk.layout     = Vk.IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL+          }+      }++    pre = zero+      { Vk.dependencyFlags = Vk.DEPENDENCY_BY_REGION_BIT++      , Vk.srcSubpass      = Vk.SUBPASS_EXTERNAL+      , Vk.srcStageMask    = Vk.PIPELINE_STAGE_FRAGMENT_SHADER_BIT+      , Vk.srcAccessMask   = Vk.ACCESS_SHADER_READ_BIT++      , Vk.dstSubpass      = 0+      , Vk.dstStageMask    = Vk.PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT+      , Vk.dstAccessMask   = Vk.ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT+      }++    post = zero+      { Vk.dependencyFlags = Vk.DEPENDENCY_BY_REGION_BIT++      , Vk.srcSubpass      = 0+      , Vk.srcStageMask    = Vk.PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT+      , Vk.srcAccessMask   = Vk.ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT++      , Vk.dstSubpass      = Vk.SUBPASS_EXTERNAL+      , Vk.dstStageMask    = Vk.PIPELINE_STAGE_FRAGMENT_SHADER_BIT+      , Vk.dstAccessMask   = Vk.ACCESS_SHADER_READ_BIT+      }++-- ** Framebuffer++type Framebuffers =+  ( RefCounted+  , Image.AllocatedImage+  , Vk.Framebuffer+  )++allocateFramebuffer+  :: ( Resource.MonadResource m+     , MonadVulkan env m+     , HasLogFunc env+     , HasSwapchain context+     )+  => context+  -> Vk.Extent2D+  -> Word32+  -> Vk.RenderPass+  -> m Framebuffers+allocateFramebuffer context extent layerCount renderPass = do+  device <- asks getDevice+  context' <- asks (, context)+  let Vk.Extent2D{width, height} = extent++  (depthKey, depth) <- Resource.allocate+    (Image.createDepthResource context' extent $ Just layerCount)+    (Image.destroyAllocatedImage context')++  let+    attachments = Vector.fromList+      [ Image.aiImageView depth+      ]++    {- XXX:+      If the render pass uses multiview, then layers must be one and each attachment+      requires a number of layers that is greater than the maximum bit index set in+      the view mask in the subpasses in which it is used.+    -}+    fbNumLayers = 1++    fbCI = zero+      { Vk.renderPass  = renderPass+      , Vk.width       = width+      , Vk.height      = height+      , Vk.attachments = attachments+      , Vk.layers      = fbNumLayers+      }+  (framebufferKey, framebuffer) <- Vk.withFramebuffer device fbCI Nothing Resource.allocate+  Debug.nameObject device framebuffer "ShadowMap.FB"++  releaseDebug <- toIO $ logDebug "Releasing ShadowMap resources"+  release <- newRefCounted do+    releaseDebug+    Resource.release depthKey+    Resource.release framebufferKey++  pure (release, depth, framebuffer)++usePass :: (MonadIO io, HasRenderPass a) => a -> Word32 -> Vk.CommandBuffer -> io r -> io r+usePass render imageIndex cb =+  Vk.cmdUseRenderPass+    cb+    (beginInfo render imageIndex)+    Vk.SUBPASS_CONTENTS_INLINE++beginInfo :: HasRenderPass a => a -> Word32 -> Vk.RenderPassBeginInfo '[]+beginInfo rp imageIndex = zero+  { Vk.renderPass  = getRenderPass rp+  , Vk.framebuffer = getFramebuffers rp ! fromIntegral imageIndex -- XXX: we only have one+  , Vk.renderArea  = getRenderArea rp+  , Vk.clearValues = getClearValues rp+  }
+ src/Render/Skybox/Pipeline.hs view
@@ -0,0 +1,98 @@+{-# LANGUAGE OverloadedLists #-}++module Render.Skybox.Pipeline+  ( Pipeline+  , allocate+  ) where++import RIO++import Control.Monad.Trans.Resource (ResourceT)+import Data.Tagged (Tagged(..))+import Vulkan.Core10 qualified as Vk+import Vulkan.Zero (Zero(..))++import Engine.Vulkan.Pipeline (Config(..))+import Engine.Vulkan.Pipeline qualified as Pipeline+import Engine.Vulkan.Types (HasVulkan, HasRenderPass(..), DsBindings)+import Render.Code (compileVert, compileFrag, glsl)+import Render.DescSets.Set0 (Scene)+import Render.DescSets.Set0.Code (set0binding0, set0binding1, set0binding3)++type Pipeline = Pipeline.Pipeline '[Scene] () ()++allocate+  :: ( HasVulkan env+     , HasRenderPass renderpass+     )+  => Vk.SampleCountFlagBits+  -> Tagged Scene DsBindings+  -> renderpass+  -> ResourceT (RIO env) Pipeline+allocate multisample (Tagged set0) = do+  fmap snd . Pipeline.allocate+    Nothing+    multisample+    zero+      { cVertexCode         = Just vertCode+      , cFragmentCode       = Just fragCode+      , cDescLayouts        = Tagged @'[Scene] [set0]+      , cCull               = Vk.CULL_MODE_NONE+      }++vertCode :: ByteString+vertCode =+  $(compileVert [glsl|+    #version 450+    #extension GL_ARB_separate_shader_objects : enable++    ${set0binding0}++    layout(location = 0) out vec3 fragUVW;++    float farZ = 0.9999; // 1 - 1e-7;++    void main() {+      vec4 pos = vec4(0.0);+      switch(gl_VertexIndex) {+          case 0: pos = vec4(-1.0,  3.0, farZ, 1.0); break;+          case 1: pos = vec4(-1.0, -1.0, farZ, 1.0); break; // XXX: swapped with 2. culling?+          case 2: pos = vec4( 3.0, -1.0, farZ, 1.0); break;+      }++      vec3 unProjected = (scene.invProjection * pos).xyz;+      unProjected *= -1;+      fragUVW = mat3(scene.invView) * unProjected;++      gl_Position = pos;+    }+  |])++fragCode :: ByteString+fragCode =+  $(compileFrag [glsl|+    #version 450+    #extension GL_ARB_separate_shader_objects : enable+    #extension GL_EXT_nonuniform_qualifier : enable++    ${set0binding0}++    ${set0binding1}+    ${set0binding3}++    layout(location = 0) in vec3 fragUVW;++    layout(location = 0) out vec4 outColor;++    void main() {+      if (scene.envCubeId > -1) {+        outColor = texture(+          samplerCube(+            cubes[nonuniformEXT(scene.envCubeId)],+            samplers[2] // XXX: linear/mip0/repeat+          ),+          fragUVW+        );+      }+    }+  |])
+ src/Render/Unlit/Colored/Model.hs view
@@ -0,0 +1,25 @@+module Render.Unlit.Colored.Model+  ( Model++  , VertexAttrs+  , vkVertexAttrs++  , InstanceAttrs+  ) where++import Geomancy (Transform, Vec4)+import Geomancy.Vec3 qualified as Vec3+import Resource.Model qualified as Model+import Vulkan.Core10 qualified as Vk+import Vulkan.NamedType ((:::))++type Model buf = Model.Indexed buf Vec3.Packed VertexAttrs++type VertexAttrs = "RGBA" ::: Vec4++type InstanceAttrs = Transform++vkVertexAttrs :: [Vk.Format]+vkVertexAttrs =+  [ Vk.FORMAT_R32G32B32A32_SFLOAT -- vColor :: vec4+  ]
+ src/Render/Unlit/Colored/Pipeline.hs view
@@ -0,0 +1,120 @@+module Render.Unlit.Colored.Pipeline+  ( Pipeline+  , allocate+  , allocateWireframe+  ) where++import RIO++import Control.Monad.Trans.Resource (ResourceT)+import Data.Tagged (Tagged(..))+import Vulkan.Core10 qualified as Vk+import Vulkan.Zero (zero)++import Engine.Vulkan.Pipeline qualified as Pipeline+import Engine.Vulkan.Types (HasVulkan, HasRenderPass(..), DsBindings)+import Render.Code (compileVert, compileFrag, glsl)+import Render.DescSets.Set0 (Scene, vertexPos, instanceTransform)+import Render.DescSets.Set0.Code (set0binding0)+import Render.Unlit.Colored.Model qualified as Model++type Config = Pipeline.Config '[Scene] Model.VertexAttrs Model.InstanceAttrs+type Pipeline = Pipeline.Pipeline '[Scene] Model.VertexAttrs Model.InstanceAttrs++allocate+  :: ( HasVulkan env+     , HasRenderPass renderpass+     )+  => Bool+  -> Vk.SampleCountFlagBits+  -> Tagged Scene DsBindings+  -> renderpass+  -> ResourceT (RIO env) Pipeline+allocate useDepth multisample tset0 rp = do+  (_, p) <- Pipeline.allocate+    Nothing+    multisample+    (config useDepth tset0)+    rp+  pure p++allocateWireframe+  :: ( HasVulkan env+     , HasRenderPass renderpass+     )+  => Bool+  -> Vk.SampleCountFlagBits+  -> Tagged Scene DsBindings+  -> renderpass+  -> ResourceT (RIO env) Pipeline+allocateWireframe useDepth multisample tset0 rp = do+  (_, p) <- Pipeline.allocate+    Nothing+    multisample+    (configWireframe useDepth tset0)+    rp+  pure p++config :: Bool -> Tagged Scene DsBindings -> Config+config useDepth (Tagged set0) = zero+  { Pipeline.cDescLayouts  = Tagged @'[Scene] [set0]+  , Pipeline.cVertexCode   = Just vertCode+  , Pipeline.cVertexInput  = vertexInput+  , Pipeline.cFragmentCode = Just fragCode+  , Pipeline.cBlend        = True+  , Pipeline.cDepthTest    = useDepth+  , Pipeline.cDepthWrite   = useDepth+  }+  where+    vertexInput = Pipeline.vertexInput+      [ vertexPos+      , (Vk.VERTEX_INPUT_RATE_VERTEX, Model.vkVertexAttrs)+      , instanceTransform+      ]++configWireframe :: Bool -> Tagged Scene DsBindings -> Config+configWireframe useDepth tset0 = (config useDepth tset0)+  { Pipeline.cTopology = Vk.PRIMITIVE_TOPOLOGY_LINE_LIST+  }++vertCode :: ByteString+vertCode =+  $(compileVert [glsl|+    #version 450+    #extension GL_ARB_separate_shader_objects : enable++    ${set0binding0}++    layout(location = 0) in vec3 vPosition;+    layout(location = 1) in vec4 vColor;++    layout(location = 2) in mat4 iModel;++    layout(location = 0) out vec4 fColor;++    void main() {+      gl_Position+        = scene.projection+        * scene.view+        * iModel+        * vec4(vPosition, 1.0);++      fColor = vColor;+      fColor.rgb * fColor.a;+    }+  |])++fragCode :: ByteString+fragCode =+  $(compileFrag [glsl|+    #version 450+    #extension GL_ARB_separate_shader_objects : enable++    layout(location = 0) in vec4 fragColor;++    layout(location = 0) out vec4 outColor;++    void main() {+      outColor = fragColor;+    }+  |])
+ src/Render/Unlit/Textured/Model.hs view
@@ -0,0 +1,216 @@+module Render.Unlit.Textured.Model+  ( Model+  , VertexAttrs+  , vkVertexAttrs++  , InstanceAttrs(..)+  , instanceAttrs++  , StorableAttrs+  , storableAttrs1++  , InstanceBuffers(..)++  , TextureParams(..)+  , vkInstanceTexture++  , allocateInstancesWith+  , allocateInstancesCoherent+  , allocateInstancesCoherent_+  , updateCoherentResize_+  , Transform+  ) where++import RIO++import Foreign (Storable(..))+import Geomancy (Transform, Vec2, Vec4)+import Geomancy.Vec3 qualified as Vec3+import RIO.Vector.Storable qualified as Storable+import UnliftIO.Resource (MonadResource, ReleaseKey, ResourceT, allocate)+import Vulkan.Core10 qualified as Vk+import Vulkan.NamedType ((:::))+import Vulkan.Zero (Zero(..))++import Engine.Vulkan.Types (HasVulkan)+import Resource.Buffer qualified as Buffer+import Resource.Model qualified as Model++type Model buf = Model.Indexed buf Vec3.Packed VertexAttrs++type VertexAttrs = "uv" ::: Vec2++vkVertexAttrs :: [Vk.Format]+vkVertexAttrs =+  [ Vk.FORMAT_R32G32_SFLOAT -- vTexCoord :: vec2+  ]++-- | Data for a single element.+data InstanceAttrs = InstanceAttrs+  { textureParams :: TextureParams+  , transformMat4 :: Transform+  }++instance Zero InstanceAttrs where+  zero = InstanceAttrs+    { textureParams = zero+    , transformMat4 = mempty+    }++instanceAttrs :: Int32 -> Int32 -> [Transform] -> InstanceAttrs+instanceAttrs samplerId textureId transforms = InstanceAttrs+  { textureParams = zero+      { tpSamplerId = samplerId+      , tpTextureId = textureId+      }+  , transformMat4 = mconcat transforms+  }++-- | Intermediate data to be shipped.+type StorableAttrs =+  ( Storable.Vector TextureParams+  , Storable.Vector Transform+  )++storableAttrs1 :: Int32 -> Int32 -> [Transform] -> StorableAttrs+storableAttrs1 samplerId textureId transforms =+  ( Storable.singleton textureParams+  , Storable.singleton transformMat4+  )+  where+    InstanceAttrs{..} = instanceAttrs+      samplerId+      textureId+      transforms++-- | GPU-bound data.+data InstanceBuffers textureStage transformStage = InstanceBuffers+  { ibTexture   :: InstanceTexture textureStage+  , ibTransform :: InstanceTransform transformStage+  }++type InstanceTexture stage = Buffer.Allocated stage TextureParams++type InstanceTransform stage = Buffer.Allocated stage Transform++instance Model.HasVertexBuffers (InstanceBuffers textureStage transformStage) where+  type VertexBuffersOf (InstanceBuffers textureStage transformStage) = InstanceAttrs++  {-# INLINE getVertexBuffers #-}+  getVertexBuffers InstanceBuffers{..} =+    [ Buffer.aBuffer ibTexture+    , Buffer.aBuffer ibTransform+    ]++  {-# INLINE getInstanceCount #-}+  getInstanceCount InstanceBuffers{..} =+    min+      (Buffer.aUsed ibTexture)+      (Buffer.aUsed ibTransform)++data TextureParams = TextureParams+  { tpScale     :: Vec2+  , tpOffset    :: Vec2+  , tpGamma     :: Vec4+  , tpSamplerId :: Int32+  , tpTextureId :: Int32+  }+  deriving (Show)++instance Zero TextureParams where+  zero = TextureParams+    { tpScale     = 1.0+    , tpOffset    = 0.0+    , tpGamma     = 1.0+    , tpSamplerId = minBound+    , tpTextureId = minBound+    }++instance Storable TextureParams where+  alignment ~_ = 4++  sizeOf ~_ = 8 + 8 + 16 + 4 + 4++  poke ptr TextureParams{..} = do+    pokeByteOff ptr  0 tpScale+    pokeByteOff ptr  8 tpOffset+    pokeByteOff ptr 16 tpGamma+    pokeByteOff ptr 32 tpSamplerId+    pokeByteOff ptr 36 tpTextureId++  peek ptr = do+    tpScale     <- peekByteOff ptr  0+    tpOffset    <- peekByteOff ptr  8+    tpGamma     <- peekByteOff ptr 16+    tpSamplerId <- peekByteOff ptr 32+    tpTextureId <- peekByteOff ptr 36+    pure TextureParams{..}++vkInstanceTexture :: [Vk.Format]+vkInstanceTexture =+  [ Vk.FORMAT_R32G32B32A32_SFLOAT -- iTextureScaleOffset :: vec4+  , Vk.FORMAT_R32G32B32A32_SFLOAT -- iTextureGamma       :: vec4+  , Vk.FORMAT_R32G32_SINT         -- iTextureIds         :: ivec2+  ]++allocateInstancesWith+  :: ( MonadResource m+     , MonadUnliftIO m+     )+  => (Vk.BufferUsageFlagBits -> Int -> Storable.Vector TextureParams -> m (InstanceTexture texture))+  -> (Vk.BufferUsageFlagBits -> Int -> Storable.Vector Transform -> m (InstanceTransform transform))+  -> (forall stage a . Buffer.Allocated stage a -> m ())+  -> [InstanceAttrs]+  -> m (ReleaseKey, InstanceBuffers texture transform)+allocateInstancesWith createTextures createTransforms bufferDestroy instances = do+  ul <- askUnliftIO+  allocate (create ul) (destroy ul)+  where+    textures   = Storable.fromList $ map textureParams instances+    transforms = Storable.fromList $ map transformMat4 instances+    numInstances = Storable.length textures++    create (UnliftIO ul) = ul do+      ibTexture   <- createTextures   Vk.BUFFER_USAGE_VERTEX_BUFFER_BIT numInstances textures+      ibTransform <- createTransforms Vk.BUFFER_USAGE_VERTEX_BUFFER_BIT numInstances transforms+      pure InstanceBuffers{..}++    destroy (UnliftIO ul) InstanceBuffers{..} = ul do+      bufferDestroy ibTexture+      bufferDestroy ibTransform++allocateInstancesCoherent+  :: ( MonadReader env m+     , HasVulkan env+     , MonadResource m+     , MonadUnliftIO m+     )+  => [InstanceAttrs]+  -> m (ReleaseKey, InstanceBuffers 'Buffer.Coherent 'Buffer.Coherent)+allocateInstancesCoherent instances = do+  context <- ask+  allocateInstancesWith+    (Buffer.createCoherent context)+    (Buffer.createCoherent context)+    (Buffer.destroy context)+    instances++allocateInstancesCoherent_+  :: (HasVulkan env)+  => Int+  -> ResourceT (RIO env) (InstanceBuffers 'Buffer.Coherent 'Buffer.Coherent)+allocateInstancesCoherent_ n =+  fmap snd $ allocateInstancesCoherent (replicate n zero)++updateCoherentResize_+  :: ( HasVulkan context+     , MonadUnliftIO m+     )+  => context+  -> InstanceBuffers 'Buffer.Coherent 'Buffer.Coherent+  -> (Storable.Vector TextureParams, Storable.Vector Transform)+  -> m (InstanceBuffers 'Buffer.Coherent 'Buffer.Coherent)+updateCoherentResize_ context InstanceBuffers{..} (textures, transforms) =+  InstanceBuffers+    <$> Buffer.updateCoherentResize_ context ibTexture textures+    <*> Buffer.updateCoherentResize_ context ibTransform transforms
+ src/Render/Unlit/Textured/Pipeline.hs view
@@ -0,0 +1,149 @@+module Render.Unlit.Textured.Pipeline+  ( Pipeline+  , allocate+  , allocateBlend+  ) where++import RIO+import Render.DescSets.Set0.Code++import Control.Monad.Trans.Resource (ResourceT)+import Data.Tagged (Tagged(..))+import Vulkan.Core10 qualified as Vk+import Vulkan.Zero (zero)++import Engine.Vulkan.Pipeline qualified as Pipeline+import Engine.Vulkan.Types (HasVulkan, HasRenderPass(..), DsBindings)+import Render.Code (compileVert, compileFrag, glsl)+import Render.DescSets.Set0 (Scene, vertexPos, instanceTransform)+import Render.Unlit.Textured.Model qualified as Model++type Config = Pipeline.Config '[Scene] Model.VertexAttrs Model.InstanceAttrs+type Pipeline = Pipeline.Pipeline '[Scene] Model.VertexAttrs Model.InstanceAttrs++allocate+  :: ( HasVulkan env+     , HasRenderPass renderpass+     )+  => Vk.SampleCountFlagBits+  -> Tagged Scene DsBindings+  -> renderpass+  -> ResourceT (RIO env) Pipeline+allocate multisample tset0 rp = do+  (_, p) <- Pipeline.allocate+    Nothing+    multisample+    (config tset0)+    rp+  pure p++allocateBlend+  :: ( HasVulkan env+     , HasRenderPass renderpass+     )+  => Vk.SampleCountFlagBits+  -> Tagged Scene DsBindings+  -> renderpass+  -> ResourceT (RIO env) Pipeline+allocateBlend multisample tset0 rp = do+  (_, p) <- Pipeline.allocate+    Nothing+    multisample+    (configBlend tset0)+    rp+  pure p++config :: Tagged Scene DsBindings -> Config+config (Tagged set0) = zero+  { Pipeline.cDescLayouts  = Tagged @'[Scene] [set0]+  , Pipeline.cVertexCode   = Just vertCode+  , Pipeline.cVertexInput  = vertexInput+  , Pipeline.cFragmentCode = Just fragCode+  }+  where+    vertexInput = Pipeline.vertexInput+      [ vertexPos -- vPosition+      , (Vk.VERTEX_INPUT_RATE_VERTEX,   Model.vkVertexAttrs)+      , (Vk.VERTEX_INPUT_RATE_INSTANCE, Model.vkInstanceTexture)+      , instanceTransform+      ]++configBlend :: Tagged Scene DsBindings -> Config+configBlend tset0 = (config tset0)+  { Pipeline.cBlend      = True+  , Pipeline.cDepthWrite = False+  }++vertCode :: ByteString+vertCode =+  $(compileVert [glsl|+    #version 450+    #extension GL_ARB_separate_shader_objects : enable++    ${set0binding0}++    // vertexPos+    layout(location = 0) in vec3 vPosition;+    // vertexAttrs+    layout(location = 1) in vec2 vTexCoord;+    // textureParams+    layout(location = 2) in  vec4 iTextureScaleOffset;+    layout(location = 3) in  vec4 iTextureGamma;+    layout(location = 4) in ivec2 iTextureIds;++    // transformMat+    layout(location = 5) in mat4 iModel;++    layout(location = 0)      out  vec2 fTexCoord;+    layout(location = 1) flat out  vec4 fTextureGamma;+    layout(location = 2) flat out ivec2 fTextureIds;++    void main() {+      gl_Position+        = scene.projection+        * scene.view+        * iModel+        * vec4(vPosition, 1.0);++      fTexCoord     = vTexCoord * iTextureScaleOffset.st + iTextureScaleOffset.pq;+      fTextureGamma = iTextureGamma;+      fTextureIds   = iTextureIds;+    }+  |])++fragCode :: ByteString+fragCode =+  $(compileFrag [glsl|+    #version 450+    #extension GL_ARB_separate_shader_objects : enable+    #extension GL_EXT_nonuniform_qualifier : enable++    ${set0binding1}+    ${set0binding2}++    layout(location = 0)      in  vec2 fTexCoord;+    layout(location = 1) flat in  vec4 fTextureGamma;+    layout(location = 2) flat in ivec2 fTextureIds;++    layout(location = 0) out vec4 oColor;++    void main() {+      vec3 color = vec3(0);+      float combinedAlpha = 0;++      if (fTextureIds.t > -1 && fTextureIds.s > -1) {+        vec4 texel = texture(+          sampler2D(+            textures[nonuniformEXT(fTextureIds.t)],+            samplers[nonuniformEXT(fTextureIds.s)]+          ),+          fTexCoord+        );+        color = pow(texel.rgb, fTextureGamma.rgb);+        combinedAlpha = texel.a * fTextureGamma.a;+      }++      // XXX: premultiply alpha due to additive blending+      oColor = vec4(color * combinedAlpha, combinedAlpha);+    }+  |])
+ src/Resource/Font.hs view
@@ -0,0 +1,67 @@+module Resource.Font+  ( Config(..)+  , allocateCollection+  , collectionTextures++  , Font(..)+  , allocateFont++  ) where++import RIO++import RIO.Vector qualified as Vector+import UnliftIO.Resource qualified as Resource+import Vulkan.Core10 qualified as Vk++import Engine.Types (StageRIO)+import Engine.Vulkan.Types (Queues)+import Resource.Font.EvanW qualified as EvanW+import Resource.Texture (Texture, Flat)+import Resource.Texture qualified as Texture+import Resource.Texture.Ktx1 qualified as Ktx1++-- * General collection tools++data Config = Config+  { configContainer :: FilePath+  , configTexture   :: FilePath+  }+  deriving (Eq, Ord, Show)++allocateCollection+  :: Traversable collection+  => Queues Vk.CommandPool+  -> collection Config+  -> StageRIO st (Resource.ReleaseKey, collection Font)+allocateCollection pools collection = do+  collected <- for collection $ allocateFont pools+  key <- Resource.register $ traverse_ Resource.release $ fmap fst collected+  pure (key, fmap snd collected)++collectionTextures :: Foldable collection => collection Font -> Vector (Texture Flat)+collectionTextures = Vector.fromList . map texture . toList++-- * Individual fonts++data Font = Font+  { container :: EvanW.Container+  , texture   :: Texture Flat+  }++allocateFont+  :: Queues Vk.CommandPool+  -> Config+  -> StageRIO st (Resource.ReleaseKey, Font)+allocateFont pools Config{..} = do+  context <- ask++  container <- EvanW.load configContainer++  createTexture <- toIO $ Ktx1.createTexture pools configTexture++  (textureKey, texture) <- Resource.allocate+    createTexture+    (Texture.destroy context)++  pure (textureKey, Font{..})
+ src/Resource/Font/EvanW.hs view
@@ -0,0 +1,159 @@+{- |+  JSON font loader for bitmaps and SDFs++  Generator: https://evanw.github.io/font-texture-generator/++  Usage (WebGL): https://evanw.github.io/font-texture-generator/example-webgl/+-}++module Resource.Font.EvanW+  ( load+  , Container(..)+  , Character(..)++  , putLine+  , PutChar(..)+  ) where++import RIO++import Data.Aeson (FromJSON, eitherDecodeFileStrict')+import Foreign qualified+import Geomancy (Vec2, vec2, pattern WithVec2)+import RIO.HashMap qualified as HashMap+import RIO.Text qualified as Text+import Vulkan.NamedType ((:::))++import Engine.UI.Layout qualified as Layout++-- * Loading++newtype FontError = FontError Text+  deriving (Eq, Ord, Show, Generic)++instance Exception FontError++data Container = Container+  { name       :: Text+  , size       :: Float+  , bold       :: Bool+  , italic     :: Bool+  , width      :: Float+  , height     :: Float+  , characters :: HashMap Char Character+  }+  deriving (Eq, Ord, Show, Generic)++data Character = Character+  { x       :: Float+  , y       :: Float+  , width   :: Float+  , height  :: Float+  , originX :: Float+  , originY :: Float+  , advance :: Float+  }+  deriving (Eq, Ord, Show, Generic)++instance FromJSON Container+instance FromJSON Character++load :: HasLogFunc env => FilePath -> RIO env Container+load fp = do+  logInfo $ "Loading font " <> fromString fp+  liftIO (eitherDecodeFileStrict' fp) >>= \case+    Left err ->+      throwM . FontError $ Text.pack err+    Right res ->+      pure res++-- * Typesetting++data PutChar = PutChar+  { pcPos    :: Vec2+  , pcSize   :: Vec2+  , pcOffset :: Vec2+  , pcScale  :: Vec2+  } deriving (Show)++instance Foreign.Storable PutChar where+  alignment ~_ = 16++  sizeOf ~_ = 32 -- 4 of pairs of floats++  peek ptr = PutChar+    <$> Foreign.peekElemOff (Foreign.castPtr ptr) 0+    <*> Foreign.peekElemOff (Foreign.castPtr ptr) 1+    <*> Foreign.peekElemOff (Foreign.castPtr ptr) 2+    <*> Foreign.peekElemOff (Foreign.castPtr ptr) 3++  poke ptr PutChar{..} = do+    Foreign.pokeElemOff (Foreign.castPtr ptr) 0 pcPos+    Foreign.pokeElemOff (Foreign.castPtr ptr) 1 pcSize+    Foreign.pokeElemOff (Foreign.castPtr ptr) 2 pcOffset+    Foreign.pokeElemOff (Foreign.castPtr ptr) 3 pcScale++putLine+  :: "WH"        ::: Vec2+  -> "XY"        ::: Vec2+  -> "Alignment" ::: Layout.Alignment+  -> "Size"      ::: Float+  -> "Font"      ::: Container+  -> "Line"      ::: [Char]+  -> ("scale" ::: Float, [PutChar])+putLine (WithVec2 cw ch) (WithVec2 cx cy) Layout.Alignment{..} targetSize font =+  (sizeScale,) . extract . foldl' step (0, 0, [])+  where+    Container+      { size   = fontSize+      , width  = atlasWidth+      , height = atlasHeight+      , characters+      } = font++    sizeScale = targetSize / fontSize++    extract (offX, _offY, bits) = do+      (WithVec2 w h, WithVec2 x y, (offset, scale)) <- bits+      let+        ax = case alignX of+          Layout.Begin  -> -cw / 2+          Layout.Middle -> -offX * sizeScale / 2+          Layout.End    -> cw / 2 - offX * sizeScale++        ay = case alignY of+          Layout.Begin  -> -ch / 2 + targetSize * 1.3+          Layout.Middle -> targetSize * 0.5+          Layout.End    -> ch / 2 - targetSize * 0.5++      pure PutChar+        { pcPos    = vec2 (cx + ax + x * sizeScale) (cy + ay + y * sizeScale)+        , pcSize   = vec2 (w * sizeScale) (h * sizeScale)+        , pcOffset = offset+        , pcScale  = scale+        }++    step (offX, offY, acc) ' ' =+      ( offX + fontSize / 2+      , offY+      , acc+      )++    step (offX, offY, acc) char =+      case HashMap.lookup char characters <|> HashMap.lookup '?' characters of+        Nothing ->+          (offX, offY, acc)+        Just Character{..} ->+          ( offX + advance+          , offY+          , ( vec2 width (-height)+            , vec2 ox oy+            , (uvOffset, uvScale)+            ) : acc+          )+          where+            ox = offX + width / 2 - originX+            oy = offY + height / 2 - originY++            uvOffset = vec2 (x / atlasWidth) (y / atlasHeight)+            uvScale  = vec2 (width / atlasWidth) (height / atlasHeight)
+ src/Resource/Mesh/Lit.hs view
@@ -0,0 +1,34 @@+{-# OPTIONS_GHC -fplugin Foreign.Storable.Generic.Plugin #-}++module Resource.Mesh.Lit+  ( MaterialNodes+  , MaterialNode(..)+  ) where++import RIO++import Foreign.Storable.Generic (GStorable)+import RIO.Vector.Storable qualified as Storable++import Render.Lit.Material (Material)+import Resource.Mesh.Types (HasRange(..), Node)++type MaterialNodes = Storable.Vector MaterialNode++data MaterialNode = MaterialNode+  { mnNode       :: Node+  , mnMaterialIx :: Int+  , mnMaterial   :: Material+  }+  deriving (Eq, Show, Generic)++instance GStorable MaterialNode++instance HasRange MaterialNode where+  {-# INLINE getRange #-}+  getRange = getRange . mnNode++  {-# INLINE adjustRange #-}+  adjustRange mn@MaterialNode{mnNode} newFirstIndex = mn+    { mnNode = adjustRange mnNode newFirstIndex+    }