#include "util/string_view.h"
#include "pipeline/pipeline.h"
#include "vulkan/vulkan.h"
+#include "util/void_t.h"
#if SDL_MAJOR_VERSION != 2
#error wrong SDL varsion
int test_main(int argc, char **argv)
{
+ if(SDL_Init(0) < 0)
+ {
+ std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl;
+ return 1;
+ }
+ struct Shutdown_sdl
+ {
+ ~Shutdown_sdl()
+ {
+ SDL_Quit();
+ }
+ } shutdown_sdl;
const char *vertex_shader_filename = "test-files/tri.vert.spv";
const char *fragment_shader_filename = "test-files/tri.frag.spv";
if(argc > 1)
};
auto graphics_pipeline =
pipeline::Graphics_pipeline::make(nullptr, graphics_pipeline_create_info);
+ VkImageCreateInfo image_create_info = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = VK_FORMAT_B8G8R8A8_UNORM,
+ .extent =
+ {
+ .width = window_width, .height = window_height, .depth = 1,
+ },
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_LINEAR,
+ .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ };
+ image::Image color_attachment(image::Image_descriptor(image_create_info),
+ image::allocate_memory_tag);
+ VkClearColorValue clear_color;
+ // set clear_color to opaque red
+ clear_color.float32[0] = 1;
+ clear_color.float32[1] = 0;
+ clear_color.float32[2] = 0;
+ clear_color.float32[3] = 1;
+ color_attachment.clear(clear_color);
+ constexpr std::uint32_t vertex_start_index = 0;
+ constexpr std::uint32_t vertex_end_index = 3;
+ constexpr std::uint32_t instance_id = 0;
+ graphics_pipeline->run(vertex_start_index, vertex_end_index, instance_id, color_attachment);
+ typedef std::uint32_t Pixel_type;
+ // check Pixel_type
+ static_assert(std::is_void<util::void_t<decltype(graphics_pipeline->run_fragment_shader(
+ static_cast<Pixel_type *>(nullptr)))>>::value,
+ "");
+ auto rgba = [](std::uint8_t r,
+ std::uint8_t g,
+ std::uint8_t b,
+ std::uint8_t a) noexcept->Pixel_type
+ {
+ union
+ {
+ Pixel_type retval;
+ std::uint8_t bytes[4];
+ };
+ static_assert(sizeof(retval) == sizeof(bytes), "");
+ bytes[0] = b;
+ bytes[1] = g;
+ bytes[2] = r;
+ bytes[3] = a;
+ return retval;
+ };
+ constexpr std::size_t bits_per_pixel = 32;
+ struct Surface_deleter
+ {
+ void operator()(SDL_Surface *v) const noexcept
+ {
+ ::SDL_FreeSurface(v);
+ }
+ };
+ std::unique_ptr<SDL_Surface, Surface_deleter> surface(
+ SDL_CreateRGBSurfaceFrom(color_attachment.memory.get(),
+ window_width,
+ window_height,
+ bits_per_pixel,
+ color_attachment.descriptor.get_memory_stride(),
+ rgba(0xFF, 0, 0, 0),
+ rgba(0, 0xFF, 0, 0),
+ rgba(0, 0, 0xFF, 0),
+ rgba(0, 0, 0, 0xFF)));
+ if(!surface)
+ throw std::runtime_error(std::string("SDL_CreateRGBSurfaceFrom failed: ") + SDL_GetError());
+ const char *output_file = "output.bmp";
+ if(SDL_SaveBMP(surface.get(), output_file) < 0)
+ throw std::runtime_error(std::string("SDL_SaveBMP failed: ") + SDL_GetError());
+ std::cerr << "saved output image to " << output_file << std::endl;
}
catch(std::runtime_error &e)
{
retval *= extent.height;
return retval;
}
+ constexpr std::size_t get_memory_stride() const noexcept
+ {
+#warning finish implementing Image
+ assert(samples == VK_SAMPLE_COUNT_1_BIT && "multisample images are unimplemented");
+ assert(extent.width > 0);
+ assert(extent.height > 0);
+ assert(extent.depth > 0);
+
+ assert(type == VK_IMAGE_TYPE_2D && "unimplemented image type");
+ assert(extent.depth == 1);
+
+ assert(format == VK_FORMAT_B8G8R8A8_UNORM && "unimplemented image format");
+ assert(mip_levels == 1 && "mipmapping is unimplemented");
+ assert(array_layers == 1 && "array images are unimplemented");
+ assert(tiling == VK_IMAGE_TILING_LINEAR && "non-linear image tiling is unimplemented");
+ std::size_t retval = sizeof(std::uint32_t);
+ retval *= extent.width;
+ return retval;
+ }
+ constexpr std::size_t get_memory_pixel_size() const noexcept
+ {
+#warning finish implementing Image
+ assert(samples == VK_SAMPLE_COUNT_1_BIT && "multisample images are unimplemented");
+ assert(extent.width > 0);
+ assert(extent.height > 0);
+ assert(extent.depth > 0);
+
+ assert(type == VK_IMAGE_TYPE_2D && "unimplemented image type");
+ assert(extent.depth == 1);
+
+ assert(format == VK_FORMAT_B8G8R8A8_UNORM && "unimplemented image format");
+ assert(mip_levels == 1 && "mipmapping is unimplemented");
+ assert(array_layers == 1 && "array images are unimplemented");
+ assert(tiling == VK_IMAGE_TILING_LINEAR && "non-linear image tiling is unimplemented");
+ std::size_t retval = sizeof(std::uint32_t);
+ return retval;
+ }
};
struct Allocate_memory_tag
: descriptor(descriptor), memory(new unsigned char[descriptor.get_memory_size()])
{
}
+ void clear(VkClearColorValue color) noexcept
+ {
+ assert(memory);
+ assert(descriptor.samples == VK_SAMPLE_COUNT_1_BIT
+ && "multisample images are unimplemented");
+ assert(descriptor.extent.width > 0);
+ assert(descriptor.extent.height > 0);
+ assert(descriptor.extent.depth > 0);
+
+ assert(descriptor.type == VK_IMAGE_TYPE_2D && "unimplemented image type");
+ assert(descriptor.extent.depth == 1);
+
+ assert(descriptor.format == VK_FORMAT_B8G8R8A8_UNORM && "unimplemented image format");
+ assert(descriptor.mip_levels == 1 && "mipmapping is unimplemented");
+ assert(descriptor.array_layers == 1 && "array images are unimplemented");
+ assert(descriptor.tiling == VK_IMAGE_TILING_LINEAR
+ && "non-linear image tiling is unimplemented");
+ union
+ {
+ std::uint8_t bytes[4];
+ std::uint32_t u32;
+ } clear_color;
+ float r_float = color.float32[0];
+ float g_float = color.float32[1];
+ float b_float = color.float32[2];
+ float a_float = color.float32[3];
+ auto float_to_byte = [](float v) noexcept->std::uint8_t
+ {
+ if(!(v >= 0))
+ v = 0;
+ else if(v > 1)
+ v = 1;
+ union
+ {
+ std::uint32_t i;
+ float f;
+ } u;
+ static_assert(sizeof(std::uint32_t) == sizeof(float), "");
+ u.f = 0x100;
+ u.i--; // u.f = nextafter(u.f, -1)
+ v *= u.f;
+ return (int)v;
+ };
+ clear_color.bytes[0] = float_to_byte(b_float);
+ clear_color.bytes[1] = float_to_byte(g_float);
+ clear_color.bytes[2] = float_to_byte(r_float);
+ clear_color.bytes[3] = float_to_byte(a_float);
+ std::size_t pixel_count =
+ static_cast<std::size_t>(descriptor.extent.width) * descriptor.extent.height;
+ std::uint32_t *pixels = reinterpret_cast<std::uint32_t *>(memory.get());
+ for(std::size_t i = 0; i < pixel_count; i++)
+ {
+ pixels[i] = clear_color.u32;
+ }
+ }
#warning finish implementing Image
};
}
void Graphics_pipeline::run(std::uint32_t vertex_start_index,
std::uint32_t vertex_end_index,
std::uint32_t instance_id,
- image::Image &color_attachment)
+ const image::Image &color_attachment)
{
+ typedef std::uint32_t Pixel_type;
+ assert(color_attachment.descriptor.tiling == VK_IMAGE_TILING_LINEAR);
+ std::size_t color_attachment_stride = color_attachment.descriptor.get_memory_stride();
+ std::size_t color_attachment_pixel_size = color_attachment.descriptor.get_memory_pixel_size();
+ unsigned char *color_attachment_memory = color_attachment.memory.get();
+ float viewport_x_scale, viewport_x_offset, viewport_y_scale, viewport_y_offset,
+ viewport_z_scale, viewport_z_offset;
+ {
+ float px = viewport.width;
+ float ox = viewport.x + 0.5f * viewport.width;
+ float py = viewport.height;
+ float oy = viewport.y + 0.5f * viewport.height;
+ float pz = viewport.maxDepth - viewport.minDepth;
+ float oz = viewport.minDepth;
+ viewport_x_scale = px * 0.5f;
+ viewport_x_offset = ox;
+ viewport_y_scale = py * 0.5f;
+ viewport_y_offset = oy;
+ viewport_z_scale = pz;
+ viewport_z_offset = oz;
+ }
constexpr std::size_t vec4_native_alignment = alignof(float) * 4;
constexpr std::size_t max_alignment = alignof(std::max_align_t);
constexpr std::size_t vec4_alignment =
vec4_native_alignment > max_alignment ? max_alignment : vec4_native_alignment;
+ constexpr std::size_t ivec4_native_alignment = alignof(std::int32_t) * 4;
+ constexpr std::size_t ivec4_alignment =
+ ivec4_native_alignment > max_alignment ? max_alignment : ivec4_native_alignment;
struct alignas(vec4_alignment) Vec4
{
float x;
{
}
};
+ struct alignas(ivec4_alignment) Ivec4
+ {
+ std::int32_t x;
+ std::int32_t y;
+ std::int32_t z;
+ std::int32_t w;
+ constexpr Ivec4() noexcept : x(), y(), z(), w()
+ {
+ }
+ constexpr explicit Ivec4(std::int32_t x,
+ std::int32_t y,
+ std::int32_t z,
+ std::int32_t w) noexcept : x(x),
+ y(y),
+ z(z),
+ w(w)
+ {
+ }
+ };
auto interpolate_float = [](float t, float v0, float v1) noexcept->float
{
return t * v1 + (1.0f - t) * v0;
std::uint32_t chunk_size = vertex_end_index - vertex_start_index;
if(chunk_size > chunk_max_size)
chunk_size = chunk_max_size;
- run_vertex_shader(vertex_start_index,
- vertex_start_index + chunk_size,
+ auto current_vertex_start_index = vertex_start_index;
+ vertex_start_index += chunk_size;
+ run_vertex_shader(current_vertex_start_index,
+ current_vertex_start_index + chunk_size,
instance_id,
chunk_vertex_buffer.get());
const unsigned char *current_vertex =
{
return vertex.w - vertex.y;
});
- [[gnu::unused]] auto rasterize_triangle = [this](Triangle triangle)
+ VkOffset2D clipped_scissor_rect_min = scissor_rect.offset;
+ VkOffset2D clipped_scissor_rect_end = {
+ .x = scissor_rect.offset.x + static_cast<std::int32_t>(scissor_rect.extent.width),
+ .y = scissor_rect.offset.y + static_cast<std::int32_t>(scissor_rect.extent.height),
+ };
+ if(clipped_scissor_rect_min.x < 0)
+ clipped_scissor_rect_min.x = 0;
+ if(clipped_scissor_rect_min.y < 0)
+ clipped_scissor_rect_min.y = 0;
+ if(clipped_scissor_rect_end.x > color_attachment.descriptor.extent.width)
+ clipped_scissor_rect_end.x = color_attachment.descriptor.extent.width;
+ if(clipped_scissor_rect_end.y < color_attachment.descriptor.extent.height)
+ clipped_scissor_rect_end.y = color_attachment.descriptor.extent.height;
+ if(clipped_scissor_rect_end.x <= clipped_scissor_rect_min.x)
+ continue;
+ if(clipped_scissor_rect_end.y <= clipped_scissor_rect_min.y)
+ continue;
+ for(std::size_t triangle_index = 0; triangle_index < triangles.size(); triangle_index++)
{
-#warning finish implementing Graphics_pipeline::run::rasterize_triangle
- assert(!"finish implementing Graphics_pipeline::run::rasterize_triangle");
+ Triangle triangle = triangles[triangle_index];
+ Vec4 projected_triangle_and_inv_w[triangle_vertex_count];
+ Vec4 framebuffer_coordinates[triangle_vertex_count];
+ for(std::size_t i = 0; i < triangle_vertex_count; i++)
+ {
+ projected_triangle_and_inv_w[i].w = 1.0f / triangle.vertexes[i].w;
+ projected_triangle_and_inv_w[i].x =
+ triangle.vertexes[i].x * projected_triangle_and_inv_w[i].w;
+ projected_triangle_and_inv_w[i].y =
+ triangle.vertexes[i].y * projected_triangle_and_inv_w[i].w;
+ projected_triangle_and_inv_w[i].z =
+ triangle.vertexes[i].z * projected_triangle_and_inv_w[i].w;
+ framebuffer_coordinates[i] =
+ Vec4(projected_triangle_and_inv_w[i].x * viewport_x_scale + viewport_x_offset,
+ projected_triangle_and_inv_w[i].y * viewport_y_scale + viewport_y_offset,
+ projected_triangle_and_inv_w[i].z * viewport_z_scale + viewport_z_offset,
+ 0);
+ }
+ float orientation = 0;
+ for(std::size_t start_vertex_index = 0, end_vertex_index = 1;
+ start_vertex_index < triangle_vertex_count;
+ start_vertex_index++)
+ {
+ float x1 = framebuffer_coordinates[start_vertex_index].x;
+ float y1 = framebuffer_coordinates[start_vertex_index].y;
+ float x2 = framebuffer_coordinates[end_vertex_index].x;
+ float y2 = framebuffer_coordinates[end_vertex_index].y;
+ orientation += x2 * y1 - x1 * y2;
+ if(++end_vertex_index >= triangle_vertex_count)
+ end_vertex_index = 0;
+ }
+ if(!(orientation < 0)
+ && !(orientation > 0)) // zero area triangle or triangle coordinate is NaN
+ continue;
+ // orientation > 0 for counter-clockwise triangle
+ // orientation < 0 for clockwise triangle
+ std::int32_t min_x, end_x, min_y, end_y;
+ bool first = true;
+ for(std::size_t i = 0; i < triangle_vertex_count; i++)
+ {
+ // x and y will be >= 0 so we can use truncate instead of floor for speed
+ auto current_min_x = static_cast<std::int32_t>(framebuffer_coordinates[i].x);
+ auto current_min_y = static_cast<std::int32_t>(framebuffer_coordinates[i].y);
+ std::int32_t current_end_x = current_min_x + 1;
+ std::int32_t current_end_y = current_min_y + 1;
+ if(first || current_min_x < min_x)
+ min_x = current_min_x;
+ if(first || current_end_x > end_x)
+ end_x = current_end_x;
+ if(first || current_min_y < min_y)
+ min_y = current_min_y;
+ if(first || current_end_y > end_y)
+ end_y = current_end_y;
+ first = false;
+ }
+ if(min_x < clipped_scissor_rect_min.x)
+ min_x = clipped_scissor_rect_min.x;
+ if(end_x > clipped_scissor_rect_end.x)
+ end_x = clipped_scissor_rect_end.x;
+ if(min_y < clipped_scissor_rect_min.y)
+ min_y = clipped_scissor_rect_min.y;
+ if(end_y > clipped_scissor_rect_end.y)
+ end_y = clipped_scissor_rect_end.y;
+ constexpr int scale = 1 << 8;
+ struct Edge_equation
+ {
+ std::int32_t a;
+ std::int32_t b;
+ std::int32_t c;
+ std::int32_t padding;
+ constexpr Edge_equation() noexcept : a(), b(), c(), padding()
+ {
+ }
+ constexpr Edge_equation(std::int32_t a, std::int32_t b, std::int32_t c) noexcept
+ : a(a),
+ b(b),
+ c(c),
+ padding()
+ {
+ }
+ constexpr bool inside(std::int32_t x, std::int32_t y) const noexcept
+ {
+ return a * x + b * y + c >= 0;
+ }
+ };
+ Edge_equation edge_equations[triangle_vertex_count];
+ for(std::size_t start_vertex_index = 0, end_vertex_index = 1, other_vertex_index = 2;
+ start_vertex_index < triangle_vertex_count;
+ start_vertex_index++)
+ {
+ float x1 = framebuffer_coordinates[start_vertex_index].x;
+ float y1 = framebuffer_coordinates[start_vertex_index].y;
+ float x2 = framebuffer_coordinates[end_vertex_index].x;
+ float y2 = framebuffer_coordinates[end_vertex_index].y;
+ [[gnu::unused]] float x3 = framebuffer_coordinates[other_vertex_index].x;
+ [[gnu::unused]] float y3 = framebuffer_coordinates[other_vertex_index].y;
+ std::int32_t a;
+ std::int32_t b;
+ std::int32_t c;
+ {
+ // solve a * x1 + b * y1 + c == 0 &&
+ // a * x2 + b * y2 + c == 0 &&
+ // max(abs(a), abs(b)) == scale &&
+ // a * x3 + b * y3 + c >= 0
+ float divisor = std::fmax(std::fabs(x2 - x1), std::fabs(y1 - y2));
+ float factor = scale / divisor;
+ float a_float = (y1 - y2) * factor;
+ float b_float = (x2 - x1) * factor;
+ float c_float = (x1 * y2 - x2 * y1) * factor;
+
+ // offset to end up checking at pixel center instead of top-left pixel corner
+ c_float += 0.5f * (a_float + b_float);
+
+ a = a_float;
+ b = b_float;
+ c = c_float;
+ if(orientation > 0)
+ {
+ // fix sign
+ a = -a;
+ b = -b;
+ c = -c;
+ }
+ }
+ // handle top-left fill rule
+ if(a < 0 || (a == 0 && b < 0))
+ {
+ // not a top-left edge, fixup c
+ c--;
+ }
+
+ edge_equations[start_vertex_index] = Edge_equation(a, b, c);
+ if(++end_vertex_index >= triangle_vertex_count)
+ end_vertex_index = 0;
+ if(++other_vertex_index >= triangle_vertex_count)
+ other_vertex_index = 0;
+ }
+ auto fs = this->fragment_shader_function;
+ for(std::int32_t y = min_y; y < end_y; y++)
+ {
+ for(std::int32_t x = min_x; x < end_x; x++)
+ {
+ bool inside = true;
+ for(auto &edge_equation : edge_equations)
+ {
+ inside &= edge_equation.inside(x, y);
+ }
+ if(inside)
+ fs(reinterpret_cast<Pixel_type *>(
+ color_attachment_memory
+ + (static_cast<std::size_t>(x) * color_attachment_pixel_size
+ + static_cast<std::size_t>(y) * color_attachment_stride)));
+ }
+ }
};
-#warning finish implementing Graphics_pipeline::run
- assert(!"finish implementing Graphics_pipeline::run");
}
}
Fragment_shader_function fragment_shader_function = nullptr;
for(auto &compiled_shader : implementation->compiled_shaders)
{
- vertex_shader_output_struct_size = implementation->jit_stack.add_eagerly_compiled_ir(
+ implementation->jit_stack.add_eagerly_compiled_ir(
std::move(compiled_shader.module),
&spirv_to_llvm::Jit_symbol_resolver::resolve,
static_cast<void *>(&implementation->jit_symbol_resolver));
#warning finish implementing Graphics_pipeline::make
if(!vertex_shader_function)
throw std::runtime_error("graphics pipeline doesn't have vertex shader");
+ if(!create_info.pViewportState)
+ throw std::runtime_error("missing viewport state");
+ if(create_info.pViewportState->viewportCount != 1)
+ throw std::runtime_error("unimplemented viewport count");
+ if(!create_info.pViewportState->pViewports)
+ throw std::runtime_error("missing viewport list");
+ if(!create_info.pViewportState->pScissors)
+ throw std::runtime_error("missing scissor rectangle list");
assert(vertex_shader_position_output_offset);
return std::unique_ptr<Graphics_pipeline>(
new Graphics_pipeline(std::move(implementation),
vertex_shader_function,
vertex_shader_output_struct_size,
*vertex_shader_position_output_offset,
- fragment_shader_function));
+ fragment_shader_function,
+ create_info.pViewportState->pViewports[0],
+ create_info.pViewportState->pScissors[0]));
}
}
}