better separation of swapchain, flight, and uber
This commit is contained in:
57
src/Uber.zig
57
src/Uber.zig
@@ -11,52 +11,42 @@ pipeline: vk.Pipeline,
|
||||
|
||||
pub const Index = u16;
|
||||
|
||||
pub const Uniform = extern struct {
|
||||
mat: [16]f32,
|
||||
|
||||
const Bindings = [_]vk.DescriptorSetLayoutBinding{.{
|
||||
.binding = 0,
|
||||
.descriptor_type = .uniform_buffer,
|
||||
.descriptor_count = 1,
|
||||
.stage_flags = .{ .vertex_bit = true },
|
||||
}};
|
||||
};
|
||||
|
||||
pub const Vertex = extern struct {
|
||||
pos: [4]f32,
|
||||
color: [3]f32,
|
||||
|
||||
const InputStateInfo = vk.PipelineVertexInputStateCreateInfo{
|
||||
.vertex_binding_description_count = 1,
|
||||
.p_vertex_binding_descriptions = &.{
|
||||
vk.VertexInputBindingDescription{
|
||||
const Bindings = [_]vk.VertexInputBindingDescription{
|
||||
.{
|
||||
.binding = 0,
|
||||
.stride = @sizeOf(Vertex),
|
||||
.input_rate = .vertex,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
.vertex_attribute_description_count = 2,
|
||||
.p_vertex_attribute_descriptions = &.{
|
||||
vk.VertexInputAttributeDescription{
|
||||
const Attributes = [_]vk.VertexInputAttributeDescription{
|
||||
.{
|
||||
.binding = 0,
|
||||
.location = 0,
|
||||
.format = .r32g32b32a32_sfloat,
|
||||
.offset = @offsetOf(Vertex, "pos"),
|
||||
},
|
||||
vk.VertexInputAttributeDescription{
|
||||
.{
|
||||
.binding = 0,
|
||||
.location = 1,
|
||||
.format = .r32g32b32_sfloat,
|
||||
.offset = @offsetOf(Vertex, "color"),
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
pub const Uniform = extern struct {
|
||||
proj: [16]f32,
|
||||
|
||||
pub const DescriptorLayoutInfo = vk.DescriptorSetLayoutCreateInfo{
|
||||
.flags = .{},
|
||||
.binding_count = 1,
|
||||
.p_bindings = &.{
|
||||
vk.DescriptorSetLayoutBinding{
|
||||
.binding = 0,
|
||||
.descriptor_type = .uniform_buffer,
|
||||
.descriptor_count = 1,
|
||||
.stage_flags = .{ .vertex_bit = true },
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -73,7 +63,11 @@ pub fn init(cache: vk.PipelineCache) !Self {
|
||||
}, null);
|
||||
defer au.D.destroyShaderModule(frag, null);
|
||||
|
||||
const set_layout = try au.D.createDescriptorSetLayout(&Uniform.DescriptorLayoutInfo, null);
|
||||
const set_layout = try au.D.createDescriptorSetLayout(&vk.DescriptorSetLayoutCreateInfo{
|
||||
.flags = .{},
|
||||
.binding_count = @intCast(Uniform.Bindings.len),
|
||||
.p_bindings = &Uniform.Bindings,
|
||||
}, null);
|
||||
errdefer au.D.destroyDescriptorSetLayout(set_layout, null);
|
||||
|
||||
const layout = try au.D.createPipelineLayout(&vk.PipelineLayoutCreateInfo{
|
||||
@@ -96,7 +90,12 @@ pub fn init(cache: vk.PipelineCache) !Self {
|
||||
.subpass = 0,
|
||||
.base_pipeline_handle = .null_handle,
|
||||
.base_pipeline_index = -1,
|
||||
.p_vertex_input_state = &Vertex.InputStateInfo,
|
||||
.p_vertex_input_state = &vk.PipelineVertexInputStateCreateInfo{
|
||||
.vertex_binding_description_count = @intCast(Vertex.Bindings.len),
|
||||
.p_vertex_binding_descriptions = &Vertex.Bindings,
|
||||
.vertex_attribute_description_count = @intCast(Vertex.Attributes.len),
|
||||
.p_vertex_attribute_descriptions = &Vertex.Attributes,
|
||||
},
|
||||
.p_input_assembly_state = &vk.PipelineInputAssemblyStateCreateInfo{
|
||||
.topology = .triangle_list,
|
||||
.primitive_restart_enable = vk.FALSE,
|
||||
|
@@ -6,7 +6,7 @@ const c = @import("c.zig");
|
||||
|
||||
pub const Bus = @import("au/Bus.zig");
|
||||
pub const SwapChain = @import("au/SwapChain.zig");
|
||||
pub const Flights = @import("au/flights.zig").Flights;
|
||||
pub const Flights = @import("au/Flights.zig");
|
||||
pub const VkAllocator = @import("au/VkAllocator.zig");
|
||||
|
||||
pub const use_debug_messenger = switch (builtin.mode) {
|
||||
|
57
src/au/Flights.zig
Normal file
57
src/au/Flights.zig
Normal file
@@ -0,0 +1,57 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vk");
|
||||
const au = @import("../au.zig");
|
||||
|
||||
const Self = @This();
|
||||
|
||||
const Flight = struct {
|
||||
acquire: vk.Semaphore = .null_handle,
|
||||
complete: vk.Semaphore = .null_handle,
|
||||
fence: vk.Fence = .null_handle,
|
||||
pool: vk.CommandPool = .null_handle,
|
||||
cmd: vk.CommandBuffer = .null_handle,
|
||||
};
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
flights: []Flight,
|
||||
idx: usize,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, n: usize) !Self {
|
||||
var self: Self = .{
|
||||
.alloc = alloc,
|
||||
.flights = try alloc.alloc(Flight, n),
|
||||
.idx = 0,
|
||||
};
|
||||
errdefer self.deinit();
|
||||
|
||||
for (self.flights) |*flight| {
|
||||
flight.acquire = try au.D.createSemaphore(&.{}, null);
|
||||
flight.complete = try au.D.createSemaphore(&.{}, null);
|
||||
flight.fence = try au.D.createFence(&.{ .flags = .{ .signaled_bit = true } }, null);
|
||||
flight.pool = try au.D.createCommandPool(&.{ .queue_family_index = au.device_config.family }, null);
|
||||
try au.D.allocateCommandBuffers(&vk.CommandBufferAllocateInfo{
|
||||
.command_buffer_count = 1,
|
||||
.command_pool = flight.pool,
|
||||
.level = .primary,
|
||||
}, @ptrCast(&flight.cmd));
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: Self) void {
|
||||
for (self.flights) |flight| {
|
||||
au.D.destroySemaphore(flight.acquire, null);
|
||||
au.D.destroySemaphore(flight.complete, null);
|
||||
au.D.destroyFence(flight.fence, null);
|
||||
au.D.freeCommandBuffers(flight.pool, 1, &.{flight.cmd});
|
||||
au.D.destroyCommandPool(flight.pool, null);
|
||||
}
|
||||
self.alloc.free(self.flights);
|
||||
}
|
||||
|
||||
pub fn next(self: *Self) Flight {
|
||||
const idx = self.idx;
|
||||
self.idx = (self.idx + 1) % self.flights.len;
|
||||
return self.flights[idx];
|
||||
}
|
@@ -101,3 +101,86 @@ pub fn getImage(self: Self, idx: u32) vk.Image {
|
||||
pub fn getView(self: Self, idx: u32) vk.ImageView {
|
||||
return self.views.items[idx];
|
||||
}
|
||||
|
||||
pub fn beginRendering(self: Self, cmd: au.CommandBufferProxy, area: vk.Rect2D, idx: u32) void {
|
||||
cmd.pipelineBarrier(
|
||||
.{ .top_of_pipe_bit = true },
|
||||
.{ .color_attachment_output_bit = true },
|
||||
.{},
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
1,
|
||||
&.{
|
||||
vk.ImageMemoryBarrier{
|
||||
.src_access_mask = .{},
|
||||
.dst_access_mask = .{ .color_attachment_write_bit = true },
|
||||
.old_layout = .undefined,
|
||||
.new_layout = .color_attachment_optimal,
|
||||
.src_queue_family_index = 0,
|
||||
.dst_queue_family_index = 0,
|
||||
.image = self.getImage(idx),
|
||||
.subresource_range = .{
|
||||
.aspect_mask = .{ .color_bit = true },
|
||||
.base_mip_level = 0,
|
||||
.level_count = 1,
|
||||
.base_array_layer = 0,
|
||||
.layer_count = 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
cmd.beginRendering(&vk.RenderingInfo{
|
||||
.render_area = area,
|
||||
.layer_count = 1,
|
||||
.view_mask = 0,
|
||||
.color_attachment_count = 1,
|
||||
.p_color_attachments = &.{
|
||||
vk.RenderingAttachmentInfo{
|
||||
.image_view = self.getView(idx),
|
||||
.image_layout = .color_attachment_optimal,
|
||||
.resolve_mode = .{},
|
||||
.resolve_image_view = .null_handle,
|
||||
.resolve_image_layout = .undefined,
|
||||
.load_op = .clear,
|
||||
.store_op = .store,
|
||||
.clear_value = .{ .color = .{ .float_32 = .{ 0, 0, 0, 1 } } },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
pub fn endRendering(self: Self, cmd: au.CommandBufferProxy, idx: u32) void {
|
||||
cmd.endRendering();
|
||||
|
||||
cmd.pipelineBarrier(
|
||||
.{ .color_attachment_output_bit = true },
|
||||
.{ .bottom_of_pipe_bit = true },
|
||||
.{},
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
1,
|
||||
&.{
|
||||
vk.ImageMemoryBarrier{
|
||||
.src_access_mask = .{ .color_attachment_write_bit = true },
|
||||
.dst_access_mask = .{},
|
||||
.old_layout = .color_attachment_optimal,
|
||||
.new_layout = .present_src_khr,
|
||||
.src_queue_family_index = 0,
|
||||
.dst_queue_family_index = 0,
|
||||
.image = self.getImage(idx),
|
||||
.subresource_range = .{
|
||||
.aspect_mask = .{ .color_bit = true },
|
||||
.base_mip_level = 0,
|
||||
.level_count = 1,
|
||||
.base_array_layer = 0,
|
||||
.layer_count = 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@@ -1,64 +0,0 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vk");
|
||||
const au = @import("../au.zig");
|
||||
|
||||
pub fn Flights(T: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
const Flight = struct {
|
||||
acquire: vk.Semaphore = .null_handle,
|
||||
complete: vk.Semaphore = .null_handle,
|
||||
fence: vk.Fence = .null_handle,
|
||||
pool: vk.CommandPool = .null_handle,
|
||||
cmd: vk.CommandBuffer = .null_handle,
|
||||
ctx: T,
|
||||
};
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
flights: []Flight,
|
||||
idx: usize,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, n: usize) !Self {
|
||||
var self: Self = .{
|
||||
.alloc = alloc,
|
||||
.flights = try alloc.alloc(Flight, n),
|
||||
.idx = 0,
|
||||
};
|
||||
errdefer self.deinit();
|
||||
|
||||
for (self.flights) |*flight| {
|
||||
flight.acquire = try au.D.createSemaphore(&.{}, null);
|
||||
flight.complete = try au.D.createSemaphore(&.{}, null);
|
||||
flight.fence = try au.D.createFence(&.{ .flags = .{ .signaled_bit = true } }, null);
|
||||
flight.pool = try au.D.createCommandPool(&.{ .queue_family_index = au.device_config.family }, null);
|
||||
try au.D.allocateCommandBuffers(&vk.CommandBufferAllocateInfo{
|
||||
.command_buffer_count = 1,
|
||||
.command_pool = flight.pool,
|
||||
.level = .primary,
|
||||
}, @ptrCast(&flight.cmd));
|
||||
flight.ctx = try T.init();
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: Self) void {
|
||||
for (self.flights) |flight| {
|
||||
au.D.destroySemaphore(flight.acquire, null);
|
||||
au.D.destroySemaphore(flight.complete, null);
|
||||
au.D.destroyFence(flight.fence, null);
|
||||
au.D.freeCommandBuffers(flight.pool, 1, &.{flight.cmd});
|
||||
au.D.destroyCommandPool(flight.pool, null);
|
||||
flight.ctx.deinit();
|
||||
}
|
||||
self.alloc.free(self.flights);
|
||||
}
|
||||
|
||||
pub fn next(self: *Self) Flight {
|
||||
const idx = self.idx;
|
||||
self.idx = (self.idx + 1) % self.flights.len;
|
||||
return self.flights[idx];
|
||||
}
|
||||
};
|
||||
}
|
178
src/main.zig
178
src/main.zig
@@ -24,7 +24,7 @@ const vertices = [_]Uber.Vertex{
|
||||
const indices = [_]Uber.Index{ 4, 5, 6, 6, 5, 7 };
|
||||
|
||||
const uniform = Uber.Uniform{
|
||||
.proj = .{
|
||||
.mat = .{
|
||||
0.5, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
@@ -32,157 +32,30 @@ const uniform = Uber.Uniform{
|
||||
},
|
||||
};
|
||||
|
||||
const Frame = struct {
|
||||
pub fn init() !Frame {
|
||||
return .{};
|
||||
}
|
||||
|
||||
pub fn deinit(self: Frame) void {
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn record_render(
|
||||
self: Frame,
|
||||
fn record_render(
|
||||
cmd: au.CommandBufferProxy,
|
||||
image: vk.Image,
|
||||
view: vk.ImageView,
|
||||
scissor: vk.Rect2D,
|
||||
pipeline: vk.Pipeline,
|
||||
layout: vk.PipelineLayout,
|
||||
uber: Uber,
|
||||
area: vk.Rect2D, // render area, scissor, and viewport.
|
||||
vertex_buffer: vk.Buffer,
|
||||
index_buffer: vk.Buffer,
|
||||
uniform_buffer: vk.Buffer,
|
||||
descriptor_set: vk.DescriptorSet,
|
||||
) !void {
|
||||
_ = self;
|
||||
|
||||
cmd.pipelineBarrier(
|
||||
.{ .top_of_pipe_bit = true },
|
||||
.{ .color_attachment_output_bit = true },
|
||||
.{},
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
1,
|
||||
@ptrCast(&vk.ImageMemoryBarrier{
|
||||
.src_access_mask = .{},
|
||||
.dst_access_mask = .{ .color_attachment_write_bit = true },
|
||||
.old_layout = .undefined,
|
||||
.new_layout = .color_attachment_optimal,
|
||||
.src_queue_family_index = 0,
|
||||
.dst_queue_family_index = 0,
|
||||
.image = image,
|
||||
.subresource_range = .{
|
||||
.aspect_mask = .{ .color_bit = true },
|
||||
.base_mip_level = 0,
|
||||
.level_count = 1,
|
||||
.base_array_layer = 0,
|
||||
.layer_count = 1,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const info = vk.RenderingInfoKHR{
|
||||
.render_area = scissor,
|
||||
.layer_count = 1,
|
||||
.view_mask = 0,
|
||||
.color_attachment_count = 1,
|
||||
.p_color_attachments = &.{vk.RenderingAttachmentInfo{
|
||||
.image_view = view,
|
||||
.image_layout = .color_attachment_optimal,
|
||||
.resolve_mode = .{},
|
||||
.resolve_image_view = .null_handle,
|
||||
.resolve_image_layout = .undefined,
|
||||
.load_op = .clear,
|
||||
.store_op = .store,
|
||||
.clear_value = .{ .color = .{ .float_32 = .{ 0, 0, 0, 1 } } },
|
||||
}},
|
||||
};
|
||||
|
||||
) void {
|
||||
cmd.setViewport(0, 1, &.{.{
|
||||
.x = @floatFromInt(scissor.offset.x),
|
||||
.y = @floatFromInt(scissor.offset.y),
|
||||
.width = @floatFromInt(scissor.extent.width),
|
||||
.height = @floatFromInt(scissor.extent.height),
|
||||
.x = @floatFromInt(area.offset.x),
|
||||
.y = @floatFromInt(area.offset.y),
|
||||
.width = @floatFromInt(area.extent.width),
|
||||
.height = @floatFromInt(area.extent.height),
|
||||
.min_depth = 0,
|
||||
.max_depth = 1,
|
||||
}});
|
||||
cmd.setScissor(0, 1, &.{scissor});
|
||||
cmd.setScissor(0, 1, &.{area});
|
||||
|
||||
cmd.beginRendering(&info);
|
||||
|
||||
cmd.bindDescriptorSets(.graphics, layout, 0, 1, &.{descriptor_set}, 0, null);
|
||||
cmd.bindPipeline(.graphics, pipeline);
|
||||
cmd.bindPipeline(.graphics, uber.pipeline);
|
||||
cmd.bindDescriptorSets(.graphics, uber.layout, 0, 1, &.{descriptor_set}, 0, null);
|
||||
cmd.bindVertexBuffers(0, 1, &.{vertex_buffer}, &.{0});
|
||||
cmd.bindIndexBuffer(index_buffer, 0, .uint16);
|
||||
cmd.drawIndexed(indices.len, 1, 0, 0, 0);
|
||||
|
||||
im.c.ImGui_ImplVulkan_RenderDrawData(im.c.igGetDrawData(), @ptrFromInt(@intFromEnum(cmd.handle)), null);
|
||||
|
||||
cmd.endRendering();
|
||||
|
||||
// vulkan implicitly ensures the host writes all data before the host reads it
|
||||
// be sure the shader reads all the vertex data before the host might modify it
|
||||
cmd.pipelineBarrier(
|
||||
.{ .all_graphics_bit = true },
|
||||
.{ .host_bit = true },
|
||||
.{},
|
||||
0,
|
||||
null,
|
||||
2,
|
||||
&.{
|
||||
vk.BufferMemoryBarrier{
|
||||
.buffer = vertex_buffer,
|
||||
.src_access_mask = .{ .shader_read_bit = true },
|
||||
.dst_access_mask = .{ .host_write_bit = true },
|
||||
.offset = 0,
|
||||
.size = vk.WHOLE_SIZE,
|
||||
.src_queue_family_index = 0,
|
||||
.dst_queue_family_index = 0,
|
||||
},
|
||||
vk.BufferMemoryBarrier{
|
||||
.buffer = uniform_buffer,
|
||||
.src_access_mask = .{ .shader_read_bit = true },
|
||||
.dst_access_mask = .{ .host_write_bit = true },
|
||||
.offset = 0,
|
||||
.size = vk.WHOLE_SIZE,
|
||||
.src_queue_family_index = 0,
|
||||
.dst_queue_family_index = 0,
|
||||
},
|
||||
},
|
||||
0,
|
||||
null,
|
||||
);
|
||||
|
||||
cmd.pipelineBarrier(
|
||||
.{ .color_attachment_output_bit = true },
|
||||
.{ .bottom_of_pipe_bit = true },
|
||||
.{},
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
1,
|
||||
@ptrCast(&vk.ImageMemoryBarrier{
|
||||
.src_access_mask = .{ .color_attachment_write_bit = true },
|
||||
.dst_access_mask = .{},
|
||||
.old_layout = .color_attachment_optimal,
|
||||
.new_layout = .present_src_khr,
|
||||
.src_queue_family_index = 0,
|
||||
.dst_queue_family_index = 0,
|
||||
.image = image,
|
||||
.subresource_range = .{
|
||||
.aspect_mask = .{ .color_bit = true },
|
||||
.base_mip_level = 0,
|
||||
.level_count = 1,
|
||||
.base_array_layer = 0,
|
||||
.layer_count = 1,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn loader_wrapper(procname: [*c]const u8, _: ?*anyopaque) callconv(.C) vk.PfnVoidFunction {
|
||||
return c.glfwGetInstanceProcAddress(au.I.handle, procname);
|
||||
@@ -199,7 +72,7 @@ pub fn main() !void {
|
||||
var sc = try au.SwapChain.init(alloc);
|
||||
defer sc.deinit();
|
||||
|
||||
var flights = try au.Flights(Frame).init(alloc, 3); // FRAMES IN FLIGHT
|
||||
var flights = try au.Flights.init(alloc, 3); // FRAMES IN FLIGHT
|
||||
defer flights.deinit();
|
||||
|
||||
const ctx = im.c.igCreateContext(null) orelse return error.igCreateContextFailed;
|
||||
@@ -328,8 +201,8 @@ pub fn main() !void {
|
||||
.dst_array_element = 0,
|
||||
.descriptor_type = .uniform_buffer,
|
||||
.descriptor_count = 1,
|
||||
.p_image_info = &[0]vk.DescriptorImageInfo{},
|
||||
.p_texel_buffer_view = &[0]vk.BufferView{},
|
||||
.p_image_info = undefined,
|
||||
.p_texel_buffer_view = undefined,
|
||||
.p_buffer_info = &.{
|
||||
vk.DescriptorBufferInfo{
|
||||
.buffer = uniform_buffer,
|
||||
@@ -381,25 +254,26 @@ pub fn main() !void {
|
||||
flight.acquire,
|
||||
.null_handle,
|
||||
);
|
||||
const image = sc.getImage(acq.image_index);
|
||||
const view = sc.getView(acq.image_index);
|
||||
|
||||
var cmd = au.CommandBufferProxy.init(flight.cmd, au.D.wrapper);
|
||||
|
||||
try cmd.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
|
||||
|
||||
try flight.ctx.record_render(
|
||||
const render_area: vk.Rect2D = .{
|
||||
.offset = .{ .x = 0, .y = 0 },
|
||||
.extent = sc.cinfo.image_extent,
|
||||
};
|
||||
|
||||
sc.beginRendering(cmd, render_area, acq.image_index);
|
||||
record_render(
|
||||
cmd,
|
||||
image,
|
||||
view,
|
||||
vk.Rect2D{ .offset = .{ .x = 0, .y = 0 }, .extent = sc.cinfo.image_extent },
|
||||
uber.pipeline,
|
||||
uber.layout,
|
||||
uber,
|
||||
render_area,
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
uniform_buffer,
|
||||
descriptorSet,
|
||||
);
|
||||
im.c.ImGui_ImplVulkan_RenderDrawData(im.c.igGetDrawData(), @ptrFromInt(@intFromEnum(cmd.handle)), null);
|
||||
sc.endRendering(cmd, acq.image_index);
|
||||
|
||||
for (vertex_data) |*v| {
|
||||
for (v.pos[0..2]) |*f| {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#version 450
|
||||
|
||||
layout (set = 0, binding = 0) uniform CameraBuffer {
|
||||
mat4 viewproj;
|
||||
mat4 mat;
|
||||
} cam;
|
||||
|
||||
layout (location = 0) in vec4 a_pos;
|
||||
@@ -10,6 +10,6 @@ layout (location = 1) in vec3 a_color;
|
||||
layout (location = 0) out vec3 v_color;
|
||||
|
||||
void main() {
|
||||
gl_Position = a_pos * cam.viewproj;
|
||||
gl_Position = a_pos * cam.mat;
|
||||
v_color = a_color;
|
||||
}
|
||||
|
Reference in New Issue
Block a user