view tests/hello_triangle.nim @ 1443:768bf1a8407b default tip main

add: hello-triangle example, did: update according readme parts
author sam <sam@basx.dev>
date Mon, 24 Feb 2025 10:09:41 +0700
parents
children
line wrap: on
line source

import ../semicongine
import ../semicongine/rendering
import ../semicongine/input

# required
initEngine("Hello triangle")

# set up a simple render pass to render the displayed frame
var renderpass = createDirectPresentationRenderPass(
  depthBuffer = false, samples = VK_SAMPLE_COUNT_1_BIT
)

# the swapchain, needs to be attached to the main renderpass
setupSwapchain(renderpass = renderpass)

# render data is used for memory management on the GPU
var renderdata = initRenderData()

type
  # define a push constant, to have something moving
  PushConstant = object
    scale: float32

  # This is how we define shaders: the interface needs to be "typed"
  # but the shader code itself can freely be written in glsl
  Shader = object
    position {.VertexAttribute.}: Vec3f
    color {.VertexAttribute.}: Vec3f
    pushConstant {.PushConstant.}: PushConstant
    fragmentColor {.Pass.}: Vec3f
    outColor {.ShaderOutput.}: Vec4f
    # code
    vertexCode: string =
      """void main() {
    fragmentColor = color;
    gl_Position = vec4(position * pushConstant.scale, 1);}"""
    fragmentCode: string =
      """void main() {
    outColor = vec4(fragmentColor, 1);}"""

  # And we also need to define our Mesh, which does describe the vertex layout
  TriangleMesh = object
    position: GPUArray[Vec3f, VertexBuffer]
    color: GPUArray[Vec3f, VertexBuffer]

# instantiate the mesh and fill with data
var mesh = TriangleMesh(
  position: asGPUArray([vec3(-0.5, -0.5), vec3(0, 0.5), vec3(0.5, -0.5)], VertexBuffer),
  color: asGPUArray([vec3(0, 0, 1), vec3(0, 1, 0), vec3(1, 0, 0)], VertexBuffer),
)

# this allocates GPU data, uploads the data to the GPU and flushes any thing that is host-cached
# this is a shortcut version, more fine-grained control is possible
assignBuffers(renderdata, mesh)
renderdata.flushAllMemory()

# Now we need to instantiate the shader as a pipeline object that is attached to a renderpass
var pipeline = createPipeline(Shader(), renderPass = renderPass)

# the main render-loop will exit if we get a kill-signal from the OS
while updateInputs():
  # starts the drawing for the next frame and provides us necesseary framebuffer and commandbuffer objects in this scope
  withNextFrame(framebuffer, commandbuffer):
    # start the main (and only) renderpass we have, needs to know the target framebuffer and a commandbuffer
    withRenderPass(
      renderPass,
      framebuffer,
      commandbuffer,
      frameWidth(),
      frameHeight(),
      vec4(0, 0, 0, 0),
    ):
      # now activate our shader-pipeline
      withPipeline(commandbuffer, pipeline):
        # and finally, draw the mesh and set a single parameter
        # more complicated setups with descriptors/uniforms are of course possible
        renderWithPushConstant(
          commandbuffer = commandbuffer,
          pipeline = pipeline,
          mesh = mesh,
          pushConstant = PushConstant(scale: 0.3),
        )

# cleanup
checkVkResult vkDeviceWaitIdle(engine().vulkan.device)
destroyPipeline(pipeline)
destroyRenderData(renderdata)
destroyRenderPass(renderpass)
destroyVulkan()