{-# OPTIONS_GHC -fplugin Foreign.Storable.Generic.Plugin #-} module Render.Unlit.Sprite.Model ( InstanceAttrs(..) , StorableAttrs , InstanceBuffer , fromTexture , fromAtlas , animate_ ) where import RIO import Engine.Vulkan.Format (HasVkFormat(..)) import Engine.Vulkan.Pipeline.Graphics (HasVertexInputBindings(..), instanceFormat) import Foreign.Storable.Generic (GStorable) import Geomancy (Vec2, Vec4, UVec2, vec2, vec4, withUVec2, withVec2) import Geomancy.Vec4 qualified as Vec4 import Render.Samplers qualified as Samplers import Resource.Buffer qualified as Buffer import Resource.Image.Atlas (Atlas) import Resource.Image.Atlas qualified as Atlas import RIO.Vector.Storable qualified as Storable import Vulkan.Core10 qualified as Vk import Vulkan.NamedType ((:::)) import Vulkan.Zero (Zero(..)) data InstanceAttrs = InstanceAttrs { vertRect :: Vec4 , fragRect :: Vec4 , tint :: Vec4 , outline :: Vec4 , animation :: Vec4 -- direction xy, number of frames, speed , textureSize :: UVec2 , samplerId :: Int32 , textureId :: Int32 } deriving (Show, Generic) -- XXX: okay, the layout matches instance GStorable InstanceAttrs instance Zero InstanceAttrs where zero = InstanceAttrs { vertRect = 0 , fragRect = 0 , tint = 0 , outline = 0 , animation = 0 , textureSize = 0 , samplerId = 0 , textureId = 0 } instance HasVkFormat InstanceAttrs where getVkFormat = [ Vk.FORMAT_R32G32B32A32_SFLOAT -- Quad scale+offset , Vk.FORMAT_R32G32B32A32_SFLOAT -- UV scale+offset , Vk.FORMAT_R32G32B32A32_SFLOAT -- Tint , Vk.FORMAT_R32G32B32A32_SFLOAT -- Outline (when enabled) , Vk.FORMAT_R32G32B32A32_SFLOAT -- Linear animation direction/duration, num frames, phase , Vk.FORMAT_R32G32_UINT -- Texture size , Vk.FORMAT_R32G32_SINT -- Sampler + texture IDs ] instance HasVertexInputBindings InstanceAttrs where vertexInputBindings = [instanceFormat @InstanceAttrs] type StorableAttrs = Storable.Vector InstanceAttrs type InstanceBuffer stage = Buffer.Allocated stage InstanceAttrs fromTexture :: Int32 -- ^ Sampler index. -> Int32 -- ^ Texture index. -> Vec2 -- ^ Sprite size. -> Vec2 -- ^ Sprite position. -> InstanceAttrs fromTexture sampler texture wh pos = InstanceAttrs { vertRect = Vec4.fromVec22 pos wh , fragRect = Vec4.fromVec22 0 1 , tint = 1 , outline = 0 , animation = 0 , textureSize = 0 , samplerId = sampler , textureId = texture } fromAtlas :: Int32 -- ^ Texture ID. -> Atlas -> Vec2 -- ^ Sprite scale, wrt. to native tile size -> Vec2 -- ^ Tile position in atlas tiles. Can be fractional when using subgrids. -> Vec2 -- ^ Sprite position. -> InstanceAttrs fromAtlas texture atlas scale atlasPos worldPos = InstanceAttrs { vertRect = Vec4.fromVec22 worldPos (tileSize * scale) , fragRect = Vec4.fromVec22 ((atlasPos + tileMargins / tileSize) * uvScale) uvScale , tint = 1 , outline = 0 , animation = 0 , textureSize = Atlas.sizePx atlas , samplerId = Samplers.nearest Samplers.indices , textureId = texture } where uvScale = Atlas.uvScale atlas tileSize = withUVec2 (Atlas.tileSizePx atlas) \tw th -> vec2 (fromIntegral tw) (fromIntegral th) tileMargins = withUVec2 (Atlas.marginPx atlas) \mx my -> withVec2 atlasPos \px py -> vec2 (fromIntegral mx * px) (fromIntegral my * py) -- | Simple animation controller with left-to-right linear cycle. animate_ :: "margin" ::: Float -> "num.frames" ::: Int -> "frame duration" ::: Float -> "phase" ::: Float -> InstanceAttrs -> InstanceAttrs animate_ margin numFrames frameDuration phase attrs = attrs { animation = vec4 (fromIntegral numFrames) frameDuration margin phase }