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 +3/−0
- LICENSE +30/−0
- README.md +15/−0
- Setup.hs +2/−0
- keid-render-basic.cabal +142/−0
- src/Engine/UI/Message.hs +129/−0
- src/Render/Basic.hs +151/−0
- src/Render/Code/Lit.hs +322/−0
- src/Render/Debug/Model.hs +218/−0
- src/Render/Debug/Pipeline.hs +151/−0
- src/Render/DescSets/Set0.hs +511/−0
- src/Render/DescSets/Set0/Code.hs +76/−0
- src/Render/DescSets/Sun.hs +165/−0
- src/Render/Font/EvanwSdf/Model.hs +75/−0
- src/Render/Font/EvanwSdf/Pipeline.hs +157/−0
- src/Render/ForwardMsaa.hs +287/−0
- src/Render/Lit/Colored/Model.hs +54/−0
- src/Render/Lit/Colored/Pipeline.hs +162/−0
- src/Render/Lit/Material.hs +74/−0
- src/Render/Lit/Material/Collect.hs +81/−0
- src/Render/Lit/Material/Model.hs +69/−0
- src/Render/Lit/Material/Pipeline.hs +237/−0
- src/Render/Lit/Textured/Model.hs +162/−0
- src/Render/Lit/Textured/Pipeline.hs +184/−0
- src/Render/ShadowMap/Pipeline.hs +90/−0
- src/Render/ShadowMap/RenderPass.hs +229/−0
- src/Render/Skybox/Pipeline.hs +98/−0
- src/Render/Unlit/Colored/Model.hs +25/−0
- src/Render/Unlit/Colored/Pipeline.hs +120/−0
- src/Render/Unlit/Textured/Model.hs +216/−0
- src/Render/Unlit/Textured/Pipeline.hs +149/−0
- src/Resource/Font.hs +67/−0
- src/Resource/Font/EvanW.hs +159/−0
- src/Resource/Mesh/Lit.hs +34/−0
+ 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+ }