Harbor

branch main
showing the latest snapshot on main
main.odin 7.3 KB · Plain text
examples/offscreen_ir/main.odin 0644 Raw
package main

import "core:fmt"
import bk "../../backend"
import compiler "../../compiler"
import gpu "../.."
import app "../../app"
import ir "../../render_ir"
import shaderkit "../../shaderkit"

Vertex :: struct {
	position: [2]f32,
	uv:       [2]f32,
}

VERTEX_SHADER :: `
vertex main
  in position: vec2 @location(0)
  in uv: vec2 @location(1)
  out position: vec4 @builtin(position)
  out uv: vec2
do
  position = vec4(position, 0.0, 1.0)
  out.uv = uv
end
`

OFFSCREEN_FRAGMENT_SHADER :: `
fragment main
  in uv: vec2
  out color: vec4
do
  let band = step(0.5, fract((uv.x + uv.y) * 8.0))
  color = mix(vec4(0.05, 0.15, 0.65, 1.0), vec4(uv.x, uv.y, 0.25, 1.0), band)
end
`

SAMPLE_FRAGMENT_SHADER :: `
group frame = 0
  @binding(0) texture offscreen_color: sampler2D
end

fragment main
  in uv: vec2
  out color: vec4
do
  color = sample(offscreen_color, uv)
end
`

compile_shader :: proc(source, name: string, stage: bk.Shader_Stage) -> (bk.Shader_Handle, bool) {
	return shaderkit.create_shader_from_source(name, stage, source, {opt_level = .Basic})
}

make_offscreen_ir :: proc() -> (frame: ir.Frame_IR, pass: ir.Pass_Handle, color: ir.Resource_Handle, sampler: ir.Resource_Handle) {
	frame = ir.init_frame_ir()
	color = ir.add_texture(&frame, "offscreen-color", 256, 256, .R8G8B8A8_UNORM, {.Color_Attachment, .Sampled}, .Transient)
	depth := ir.add_texture(&frame, "offscreen-depth", 256, 256, .D32_SFLOAT, {.Depth_Stencil_Attachment}, .Transient)
	sampler = ir.add_sampler(&frame, "offscreen-sampler", {
		mag_filter = .Nearest,
		min_filter = .Nearest,
		address_mode_u = .Clamp_To_Edge,
		address_mode_v = .Clamp_To_Edge,
		mipmap_mode = .Nearest,
	}, .Transient)

	desc := ir.Pass_Desc{kind = .Render, name = "offscreen"}
	_ = ir.pass_add_color_target(&desc, ir.make_color_target(color, .R8G8B8A8_UNORM, .Clear, .Store, {0, 0, 0, 1}))
	ir.pass_set_depth_target(&desc, ir.make_depth_target(depth, .D32_SFLOAT, .Clear, .Dont_Care, 1))
	ir.pass_set_viewport(&desc, {x = 0, y = 0, width = 256, height = 256, min_depth = 0, max_depth = 1})
	ir.pass_set_scissor(&desc, {x = 0, y = 0, width = 256, height = 256})
	pass = ir.add_pass(&frame, desc)
	return
}

create_quad_buffers :: proc(backend: ^bk.Backend) -> (bk.Buffer_Handle, bk.Buffer_Handle, bool) {
	vertices := [?]Vertex{
		{position = {-0.85, -0.85}, uv = {0, 1}},
		{position = { 0.85, -0.85}, uv = {1, 1}},
		{position = { 0.85,  0.85}, uv = {1, 0}},
		{position = {-0.85,  0.85}, uv = {0, 0}},
	}
	indices := [?]u32{0, 1, 2, 2, 3, 0}

	vb, vb_ok := backend.create_buffer_staged(raw_data(vertices[:]), len(vertices) * size_of(Vertex), {.Vertex})
	if !vb_ok {
		return {}, {}, false
	}
	ib, ib_ok := backend.create_buffer_staged(raw_data(indices[:]), len(indices) * size_of(u32), {.Index})
	if !ib_ok {
		backend.destroy_buffer(vb)
		return {}, {}, false
	}
	return vb, ib, true
}

create_pipeline :: proc(
	vs, fs: bk.Shader_Handle,
	render_pass: bk.Render_Pass_Handle,
	descriptor_layouts: []bk.Descriptor_Handle = nil,
) -> (bk.Pipeline_Handle, bool) {
	bindings := [1]bk.Vertex_Binding{{binding = 0, stride = size_of(Vertex)}}
	attrs := [2]bk.Vertex_Attribute{
		{location = 0, offset = 0, format = .Float2},
		{location = 1, offset = 8, format = .Float2},
	}
	return gpu.create_pipeline({
		vert_shader = vs,
		frag_shader = fs,
		render_pass = render_pass,
		topology = .Triangle_List,
		cull_mode = .None,
		front_face = .Counter_Clockwise,
		enable_blending = false,
		blend_mode = .Alpha,
		enable_depth_test = false,
		depth_only = false,
		descriptor_layouts = descriptor_layouts,
		vertex_bindings = bindings[:],
		vertex_attributes = attrs[:],
	})
}

main :: proc() {
	when gpu.REQUIRED_SHADER_FORMAT != .SPIRV {
		fmt.println("offscreen_ir: Vulkan runtime proof only; D3D executor parity is tracked separately")
		return
	}

	state, ok := app.init_window(800, 600, "GPU - Offscreen IR")
	if !ok {
		return
	}
	defer app.shutdown(&state)

	backend := gpu.get_backend()
	if backend == nil {
		return
	}

	frame, offscreen_pass, color_resource, sampler_resource := make_offscreen_ir()
	defer ir.destroy_frame_ir(&frame)

	exec := compiler.init_execution_state(backend)
	defer compiler.destroy_execution_state(&exec)
	if !compiler.prepare_render_targets(&exec, &frame) {
		return
	}

	offscreen_render_pass, rp_ok := compiler.prepared_render_pass(&exec, offscreen_pass)
	if !rp_ok {
		return
	}
	offscreen_texture, tex_ok := compiler.prepared_texture(&exec, color_resource)
	offscreen_sampler, sampler_ok := compiler.prepared_sampler(&exec, sampler_resource)
	if !tex_ok || !sampler_ok {
		return
	}

	vs, vs_ok := compile_shader(VERTEX_SHADER, "offscreen_ir.vert", .Vertex)
	if !vs_ok { return }
	defer gpu.destroy_shader(vs)
	fs_offscreen, fs_offscreen_ok := compile_shader(OFFSCREEN_FRAGMENT_SHADER, "offscreen_ir_offscreen.frag", .Fragment)
	if !fs_offscreen_ok { return }
	defer gpu.destroy_shader(fs_offscreen)
	fs_sample, fs_sample_ok := compile_shader(SAMPLE_FRAGMENT_SHADER, "offscreen_ir_sample.frag", .Fragment)
	if !fs_sample_ok { return }
	defer gpu.destroy_shader(fs_sample)

	offscreen_pipeline, offscreen_pipeline_ok := create_pipeline(vs, fs_offscreen, offscreen_render_pass)
	if !offscreen_pipeline_ok { return }
	defer gpu.destroy_pipeline(offscreen_pipeline)

	descriptor_layout, descriptor_layout_ok := backend.create_descriptor_set_layout({{
		binding = 0,
		type = .Combined_Image_Sampler,
		count = 1,
		stages = {.Fragment},
	}})
	if !descriptor_layout_ok { return }
	defer backend.destroy_descriptor_set_layout(descriptor_layout)

	descriptor_types := [1]bk.Descriptor_Type{.Combined_Image_Sampler}
	descriptor_counts := [1]u32{1}
	descriptor_pool, descriptor_pool_ok := backend.create_descriptor_pool(1, descriptor_types[:], descriptor_counts[:])
	if !descriptor_pool_ok { return }
	defer backend.destroy_descriptor_pool(descriptor_pool)

	descriptor_set, descriptor_set_ok := backend.allocate_descriptor_set(descriptor_pool, descriptor_layout)
	if !descriptor_set_ok { return }
	backend.update_descriptor_image(descriptor_set, 0, offscreen_texture, offscreen_sampler, .Shader_Read_Only)

	descriptor_layouts := [1]bk.Descriptor_Handle{descriptor_layout}
	sample_pipeline, sample_pipeline_ok := create_pipeline(vs, fs_sample, gpu.get_default_render_pass(), descriptor_layouts[:])
	if !sample_pipeline_ok { return }
	defer gpu.destroy_pipeline(sample_pipeline)

	vertex_buffer, index_buffer, buffers_ok := create_quad_buffers(backend)
	if !buffers_ok { return }
	defer backend.destroy_buffer(index_buffer)
	defer backend.destroy_buffer(vertex_buffer)

	wrote_offscreen := false
	for !app.window_should_close(&state) {
		app.begin_drawing(&state, gpu.BLACK)
		ctx := gpu.get_frame_context()

		if !wrote_offscreen {
			if compiler.begin_prepared_pass(&exec, ctx, offscreen_pass) {
				backend.bind_graphics_pipeline(ctx, offscreen_pipeline)
				backend.bind_vertex_buffer(ctx, vertex_buffer)
				backend.bind_index_buffer(ctx, index_buffer)
				backend.draw_indexed(ctx, 6, 1, 0, 0, 0)
				compiler.end_prepared_pass(&exec, ctx)
				wrote_offscreen = true
			}
		}

		if gpu.ensure_default_render_pass() {
			backend.bind_graphics_pipeline(ctx, sample_pipeline)
			backend.bind_descriptor_set(ctx, sample_pipeline, descriptor_set, 0)
			backend.bind_vertex_buffer(ctx, vertex_buffer)
			backend.bind_index_buffer(ctx, index_buffer)
			backend.draw_indexed(ctx, 6, 1, 0, 0, 0)
		}

		app.end_drawing(&state)
		if app.is_key_pressed(&state, .Escape) {
			break
		}
	}
}