From ed7ccd0e8c094fb495133218ba4c334f232826d1 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 22 Aug 2017 17:37:09 -0700 Subject: [PATCH] draws triangle properly --- .gitignore | 1 + src/demo/demo.cpp | 92 ++++++++++++ src/image/image.h | 92 ++++++++++++ src/llvm_wrapper/llvm_wrapper.cpp | 7 + src/pipeline/pipeline.cpp | 242 ++++++++++++++++++++++++++++-- src/pipeline/pipeline.h | 12 +- 6 files changed, 433 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index bc08171..347870a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ compile_commands.json *.kdev4 /.cproject /.project +/output.bmp diff --git a/src/demo/demo.cpp b/src/demo/demo.cpp index b351bd0..20b5d47 100644 --- a/src/demo/demo.cpp +++ b/src/demo/demo.cpp @@ -34,6 +34,7 @@ #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 @@ -242,6 +243,18 @@ pipeline::Pipeline_layout_handle make_pipeline_layout() 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) @@ -456,6 +469,85 @@ int test_main(int argc, char **argv) }; 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_voidrun_fragment_shader( + static_cast(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 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) { diff --git a/src/image/image.h b/src/image/image.h index b3c62ab..3ed1667 100644 --- a/src/image/image.h +++ b/src/image/image.h @@ -111,6 +111,43 @@ struct Image_descriptor 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 @@ -135,6 +172,61 @@ struct Image : 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(descriptor.extent.width) * descriptor.extent.height; + std::uint32_t *pixels = reinterpret_cast(memory.get()); + for(std::size_t i = 0; i < pixel_count; i++) + { + pixels[i] = clear_color.u32; + } + } #warning finish implementing Image }; } diff --git a/src/llvm_wrapper/llvm_wrapper.cpp b/src/llvm_wrapper/llvm_wrapper.cpp index e1a4b64..46c6cfd 100644 --- a/src/llvm_wrapper/llvm_wrapper.cpp +++ b/src/llvm_wrapper/llvm_wrapper.cpp @@ -53,6 +53,13 @@ void Context::init_helper() throw std::runtime_error("LLVMInitializeNativeAsmPrinter failed"); if(::LLVMInitializeNativeDisassembler() != 0) throw std::runtime_error("LLVMInitializeNativeDisassembler failed"); + static struct LLVM_shutdown + { + ~LLVM_shutdown() + { + ::LLVMShutdown(); + } + } llvm_shutdown; } LLVM_string Target::get_process_target_triple() diff --git a/src/pipeline/pipeline.cpp b/src/pipeline/pipeline.cpp index de12963..1f20c32 100644 --- a/src/pipeline/pipeline.cpp +++ b/src/pipeline/pipeline.cpp @@ -409,12 +409,36 @@ void Graphics_pipeline::dump_vertex_shader_output_struct(const void *output_stru 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; @@ -431,6 +455,25 @@ void Graphics_pipeline::run(std::uint32_t vertex_start_index, { } }; + 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; @@ -568,8 +611,10 @@ void Graphics_pipeline::run(std::uint32_t vertex_start_index, 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 = @@ -628,13 +673,180 @@ void Graphics_pipeline::run(std::uint32_t vertex_start_index, { 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(scissor_rect.extent.width), + .y = scissor_rect.offset.y + static_cast(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(framebuffer_coordinates[i].x); + auto current_min_y = static_cast(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( + color_attachment_memory + + (static_cast(x) * color_attachment_pixel_size + + static_cast(y) * color_attachment_stride))); + } + } }; -#warning finish implementing Graphics_pipeline::run - assert(!"finish implementing Graphics_pipeline::run"); } } @@ -709,7 +921,7 @@ std::unique_ptr Graphics_pipeline::make( 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(&implementation->jit_symbol_resolver)); @@ -811,13 +1023,23 @@ std::unique_ptr Graphics_pipeline::make( #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( 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])); } } } diff --git a/src/pipeline/pipeline.h b/src/pipeline/pipeline.h index 2667da5..8850d82 100644 --- a/src/pipeline/pipeline.h +++ b/src/pipeline/pipeline.h @@ -229,7 +229,7 @@ public: void 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); static std::unique_ptr make(Pipeline_cache *pipeline_cache, const VkGraphicsPipelineCreateInfo &create_info); static std::unique_ptr move_from_handle(VkPipeline pipeline) noexcept @@ -248,12 +248,16 @@ private: Vertex_shader_function vertex_shader_function, std::size_t vertex_shader_output_struct_size, std::size_t vertex_shader_position_output_offset, - Fragment_shader_function fragment_shader_function) noexcept + Fragment_shader_function fragment_shader_function, + VkViewport viewport, + VkRect2D scissor_rect) noexcept : implementation(std::move(implementation)), vertex_shader_function(vertex_shader_function), vertex_shader_output_struct_size(vertex_shader_output_struct_size), vertex_shader_position_output_offset(vertex_shader_position_output_offset), - fragment_shader_function(fragment_shader_function) + fragment_shader_function(fragment_shader_function), + viewport(viewport), + scissor_rect(scissor_rect) { } @@ -263,6 +267,8 @@ private: std::size_t vertex_shader_output_struct_size; std::size_t vertex_shader_position_output_offset; Fragment_shader_function fragment_shader_function; + VkViewport viewport; + VkRect2D scissor_rect; }; inline VkPipeline to_handle(Graphics_pipeline *pipeline) noexcept -- 2.30.2