packages feed

keid-render-basic-0.1.6.1: src/Render/Unlit/Sprite/Pipeline.hs

{-# LANGUAGE OverloadedLists #-}

module Render.Unlit.Sprite.Pipeline
  ( Pipeline
  , allocate
  -- , allocateOutline
  ) where

import RIO

import Control.Monad.Trans.Resource (ResourceT)
import Data.Tagged (Tagged(..))
import Vulkan.Core10 qualified as Vk

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.Unlit.Sprite.Model qualified as Model

type Config = Pipeline.Configure Pipeline (Float, Bool)
type Pipeline = Pipeline.Pipeline '[Scene] () Model.InstanceAttrs

allocate
  :: ( HasVulkan env
     , HasRenderPass renderpass
     )
  => Vk.SampleCountFlagBits
  -> Maybe Float
  -> Bool
  -> Tagged Scene DsBindings
  -> renderpass
  -> ResourceT (RIO env) Pipeline
allocate multisample discardAlpha outline set0 = do
  fmap snd . Pipeline.allocate
    Nothing
    multisample
    (config discardAlpha outline set0)

config
  :: Maybe Float
  -> Bool
  -> Tagged Scene DsBindings
  -> Config
config discardAlpha outline (Tagged set0) =
  Pipeline.baseConfig
    { Pipeline.cVertexCode     = Just vertCode
    , Pipeline.cFragmentCode   = Just fragCode
    , Pipeline.cDescLayouts    = Tagged @'[Scene] [set0]
    , Pipeline.cVertexInput    = vertexInput
    , Pipeline.cDepthTest      = False
    , Pipeline.cDepthWrite     = False
    , Pipeline.cBlend          = True
    , Pipeline.cCull           = Vk.CULL_MODE_NONE
    , Pipeline.cSpecialization = specs
    }
  where
    vertexInput = Pipeline.vertexInput
      [ (Vk.VERTEX_INPUT_RATE_INSTANCE, Model.vkInstanceAttrs)
      ]

    specs =
      ( fromMaybe 0.0 discardAlpha
      , outline
      )

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 iTint;
    layout(location = 3) in  vec4 iOutline;
    layout(location = 4) in ivec2 iTextureIds;
    layout(location = 5) in uvec2 iTextureSize;

    layout(location = 0)      out  vec2 fTexCoord;
    layout(location = 1) flat out  vec4 fTint;
    layout(location = 2) flat out  vec4 fOutline;
    layout(location = 3) flat out ivec2 fTextureIds;
    layout(location = 4) flat out uvec2 fTextureSize;

    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
        * vec4(iVert.xy + pos * iVert.zw, 0, 1.0);

      vec2 uv = texCoords[gl_VertexIndex];
      fTexCoord = iFrag.xy + uv * iFrag.zw;

      fTint = iTint;
      fOutline = iOutline;
      fTextureIds = iTextureIds;
      fTextureSize = iTextureSize;
    }
  |])

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 fTint;
    layout(location = 2) flat in  vec4 fOutline;
    layout(location = 3) flat in ivec2 fTextureIds;
    layout(location = 4) flat in uvec2 fTextureSize;

    layout(location = 0) out vec4 outColor;

    layout(constant_id=0) const float discardAlpha = 0.0;
    layout(constant_id=1) const bool  doOutline = false;

    vec4 tap(vec2 uv) {
      return texture(
        sampler2D(
          textures[nonuniformEXT(fTextureIds.t)],
          samplers[nonuniformEXT(fTextureIds.s)]
        ),
        fTexCoord + uv
      );
    }

    vec2 step_size = 1.0 / vec2(fTextureSize);

    void main() {
      vec4 texel = tap(vec2(0));

      if (discardAlpha != 0.0) {
        if (texel.a < discardAlpha) {
          discard;
        }
      }

      outColor = texel * fTint * texel.a;

      if (doOutline) {
        if (fOutline.a > 0.0 && texel.a < 1.0) {
          float alpha = 0.0;

          alpha += tap(vec2(step_size.x,  0.0)         ).a / 4.0;
          alpha += tap(vec2(-step_size.x, 0.0)         ).a / 4.0;
          alpha += tap(vec2(0.0,          step_size.y) ).a / 4.0;
          alpha += tap(vec2(0.0,          -step_size.y)).a / 4.0;

          outColor += fOutline * alpha;
        }
      }
    }
  |])