Skip to content

Commit

Permalink
vulkanize: Add vulkan renderer
Browse files Browse the repository at this point in the history
It's all led up to this!

This adds the vulkan rendererer that implments the renderer interface
previously implemented by OpenGL. It introduces a few tweaks to that
interface as well. Additional entry points for the beginning and end of
the frame are added and calls to finalize rendering and destroy the
renderer state. The prototypes of a few existing calls have had
information added because it's needed by vulkan.

The cmake file now has a VULKANIZE flag that links appropriate libraries
as well as the appropriate *renderer object and sets defines that change
a few lines of code in source files.

It would be extremely difficult to give much detail about the vkrenderer
added here. As for broader design principles, there are a number of
cases where flexibility is consciously limited based on the requirements
of the application. This includes formats and layouts being explicitly
specified and also the limitations of what extensions and features can
be enabled.

In general, the interface is still based on GL and so there is some
sense of a minor GL on vulkan vibe. The preceding changes to the
renderer interface were made to allow for this kind of abstraction.

In general, the established practice of naming creation functions gen_*
and destruction functions del_* is maintained. Internally, create_* is
used to create internal representations and anything that is not an API
function is made static and thrown inline. I considered hiving off a lot
of the internal code to another module and making vkrenderer a
translation layer, but I didn't feel like designing another API.

To a large degree, the renderer centers around the usual standard
objects that any vulkan app might have, instance, device, queue, render
pass, swapchain, and various per-frame objects that amount to
framebuffers and command buffers along with sync objects to keep it all
in line. The user-controlled objects are exactly as they were for GL,
Images (textures), Buffers, Uniforms, and Pipelines. The one with the
greatest difference is of course the pipeline, which contains a lot more
than GL programs do. I expect that's pretty standard though. Image is a
bit broader than just textures, but externally, that's its only use.
Buffers have the most gotchas. The app previously created and deleted
them a lot and that's a problem if you don't have OpenGL around to keep
track of when its safe to actually delete them. Uniforms are just
per-frame descriptor sets and UBOs.
  • Loading branch information
pow2clk committed Jun 19, 2019
1 parent eece721 commit 33224b1
Show file tree
Hide file tree
Showing 5 changed files with 1,818 additions and 35 deletions.
25 changes: 22 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@ cmake_minimum_required(VERSION 2.8)

project(craft)

set(VULKANIZE 1)

FILE(GLOB SOURCE_FILES src/*.c)
FILE(GLOB VERTEX_SHADERS shaders/*_vertex.glsl)
FILE(GLOB FRAGMENT_SHADERS shaders/*_fragment.glsl)

if(VULKANIZE)
LIST(REMOVE_ITEM SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/glrenderer.c)
else(VULKANIZE)
LIST(REMOVE_ITEM SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/vkrenderer.c)
endif(VULKANIZE)

foreach(VERT ${VERTEX_SHADERS})
get_filename_component(BASE_NAME ${VERT} NAME_WLE)
set(SPV "shaders/${BASE_NAME}.spv")
Expand Down Expand Up @@ -41,7 +49,12 @@ add_executable(
deps/sqlite/sqlite3.c
deps/tinycthread/tinycthread.c)

add_definitions(-std=c99 -O3 -Wall)
if(VULKANIZE)
add_definitions(-std=c99 -O3 -Wall -DVULKANIZE=1 -DGLFW_INCLUDE_VULKAN)
else(VULKANIZE)
add_definitions(-std=c99 -O3 -Wall -DVULKANIZE=0)
endif(VULKANIZE)

add_dependencies(craft
Shaders
)
Expand All @@ -61,17 +74,23 @@ endif()
find_package(CURL REQUIRED)
include_directories(${CURL_INCLUDE_DIR})

if(VULKANIZE)
set(GRAPHICS_LIB vulkan)
else(VULKANIZE)
set(GRAPHICS_LIB GLEW)
endif(VULKANIZE)

if(APPLE)
target_link_libraries(craft glfw GLEW
${GLFW_LIBRARIES} ${CURL_LIBRARIES})
endif()

if(UNIX)
target_link_libraries(craft dl glfw GLEW
target_link_libraries(craft dl glfw ${GRAPHICS_LIB}
${GLFW_LIBRARIES} ${CURL_LIBRARIES})
endif()

if(MINGW)
target_link_libraries(craft ws2_32.lib glfw GLEW
target_link_libraries(craft ws2_32.lib glfw ${GRAPHICS_LIB}
${GLFW_LIBRARIES} ${CURL_LIBRARIES})
endif()
48 changes: 42 additions & 6 deletions src/glrenderer.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ struct PipelineObj {
// Initialize persistent global state for the renderer
// For GL this involves enabling depth testing, face culling,
// and settng the clear color. returns 0 on success.
int init_renderer() {
int init_renderer(GLFWwindow *window) {
glfwMakeContextCurrent(window);
glfwSwapInterval(VSYNC);

if (glewInit() != GLEW_OK) {
return -1;
}
Expand Down Expand Up @@ -136,7 +139,7 @@ void update_faces(Buffer buffer, int components, int faces, float *data) {
// <source> is expected to be a null terminated string pointing to valid GLSL
// the source is compiled and a shader is created. If compilation fails, an error message
// is printed and the returned handle is invalid
GLuint make_shader(GLenum type, const char *source, int length) {
static GLuint make_shader(GLenum type, const char *source, int length) {
GLuint shader = glCreateShader(type);
glShaderBinary(1, &shader, GL_SHADER_BINARY_FORMAT_SPIR_V, source, length);
glSpecializeShader(shader, "main", 0, NULL, NULL);
Expand Down Expand Up @@ -385,9 +388,13 @@ void draw_sky(Buffer buffer) {
draw_triangles_3d(buffer, 512 * 3);
} // draw_sky()

// Create a uniform interface object containing a ubo of size <ubo_size>
// Create a uniform object containing a ubo of size <ubo_size> to be used in <ubo_stages>
// and textures <texture0> and <texture1> then return the handle.
Uniform gen_uniform(uint32_t ubo_size, Image texture0, Image texture1) {
// GL has not use for ubo_stages
Uniform gen_uniform(uint32_t ubo_size, uint32_t ubo_stages, Image texture0, Image texture1) {

// GL has no use for this
(void)ubo_stages;

GLuint buffer;
glGenBuffers(1, &buffer);
Expand Down Expand Up @@ -452,11 +459,18 @@ void del_image(Image image) {
} // del_image()

// Create pipeline object containing shaders as extracted from files <path1> and <path2>
// useable with <uniform> with <attrib_ct> attribs containing <components> enabling <feature_bits>
// Since the only member of the GL pipeline is the program, this accomplishes no more than
// load program with a thin wrapper.
Pipeline gen_pipeline(const char *path1, const char *path2) {
// load program with a thin wrapper while ignoring most of the latter parameters.
Pipeline gen_pipeline(const char *path1, const char *path2, Uniform uniform,
uint32_t attrib_ct, const uint32_t *components, uint32_t feature_bits) {
Pipeline ret_pipeline = malloc(sizeof(struct PipelineObj));
ret_pipeline->program = load_program(path1, path2);

(void)attrib_ct;
(void)components;
(void)feature_bits;

return ret_pipeline;
} // gen_pipeline()

Expand Down Expand Up @@ -491,3 +505,25 @@ void del_pipeline(Pipeline pipeline) {
glDeleteProgram(pipeline->program);
free(pipeline);
} // del_pipeline()

// Perform any initialization or setup required at the start of the frame rendering
void start_frame() {
// GL doesn't need to do anything here.
} // start_frame()

// Perform any shutdown or submission required at the end of the frame rendering to <window>
// For GL, it's just a swap
void end_frame(GLFWwindow *window) {
glfwSwapBuffers(window);
} // end_frame()

// Conclude any rendering by the renderer in preparation for deletion
// GL has nothing to do here.
void shutdown_renderer() {
} // shutdown_renderer

// Destroy all renderer resources and free any memory
// GL just calls the glfw function
void del_renderer() {
glfwTerminate();
} // del_renderer()
69 changes: 48 additions & 21 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,8 @@ void gen_sign_buffer(Chunk *chunk) {
data + faces * 30, e->x, e->y, e->z, e->face, e->text);
}

if (!faces) return;

del_buffer(chunk->sign_buffer);
chunk->sign_buffer = gen_faces(5, faces, data);
chunk->sign_faces = faces;
Expand Down Expand Up @@ -1725,7 +1727,7 @@ void render_item(Pipeline pipeline, Uniform uniform, Buffer buffer) {
// Generate a dynamic buffer for text rendering with attrib <components>
// and <faces> characters
Buffer gen_text_buffer(int components, int faces) {
return gen_dynamic_buffer(sizeof(GLfloat) * 6 * components * faces, NULL);
return gen_dynamic_buffer(sizeof(float) * 6 * components * faces, NULL);
} // gen_text_buffer()

// Render UI <text> in 2D at screen depth located at <x,y> scaled by <n>
Expand Down Expand Up @@ -2312,6 +2314,8 @@ void create_window() {
window_width = modes[mode_count - 1].width;
window_height = modes[mode_count - 1].height;
}
if(VULKANIZE)
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
g->window = glfwCreateWindow(
window_width, window_height, "Craft", monitor, NULL);
}
Expand Down Expand Up @@ -2542,15 +2546,13 @@ int main(int argc, char **argv) {
return -1;
}

glfwMakeContextCurrent(g->window);
glfwSwapInterval(VSYNC);
glfwSetInputMode(g->window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwSetKeyCallback(g->window, on_key);
glfwSetCharCallback(g->window, on_char);
glfwSetMouseButtonCallback(g->window, on_mouse_button);
glfwSetScrollCallback(g->window, on_scroll);

if (init_renderer())
if (init_renderer(g->window))
return -1;

// LOAD TEXTURES //
Expand All @@ -2560,18 +2562,30 @@ int main(int argc, char **argv) {
Image sign = load_tex_image("textures/sign.png", 0, 0);

// CREATE UNIFORMS //
Uniform block_uniform = gen_uniform(sizeof(BlockUbo), texture, sky);
Uniform line_uniform = gen_uniform(sizeof(MatUbo), NULL, NULL);
Uniform text_uniform = gen_uniform(sizeof(MatUbo), font, NULL);
Uniform sign_uniform = gen_uniform(sizeof(MatUbo), sign, NULL);
Uniform sky_uniform = gen_uniform(sizeof(SkyUbo), sky, NULL);
Uniform block_uniform = gen_uniform(sizeof(BlockUbo), STAGE_VERT_BIT|STAGE_FRAG_BIT, texture, sky);
Uniform item_uniform = gen_uniform(sizeof(BlockUbo), STAGE_VERT_BIT|STAGE_FRAG_BIT, texture, sky);
Uniform line_uniform = gen_uniform(sizeof(MatUbo), STAGE_VERT_BIT, NULL, NULL);
Uniform wire_uniform = gen_uniform(sizeof(MatUbo), STAGE_VERT_BIT, NULL, NULL);
Uniform text_uniform = gen_uniform(sizeof(MatUbo), STAGE_VERT_BIT, font, NULL);
Uniform sign_uniform = gen_uniform(sizeof(MatUbo), STAGE_VERT_BIT, sign, NULL);
Uniform sky_uniform = gen_uniform(sizeof(SkyUbo), STAGE_VERT_BIT|STAGE_FRAG_BIT, sky, NULL);

// LOAD SHADERS //
Pipeline block_pipeline = gen_pipeline("shaders/block_vertex.spv", "shaders/block_fragment.spv");
Pipeline line_pipeline = gen_pipeline("shaders/line_vertex.spv", "shaders/line_fragment.spv");
Pipeline text_pipeline = gen_pipeline("shaders/text_vertex.spv", "shaders/text_fragment.spv");
Pipeline sign_pipeline = gen_pipeline("shaders/sign_vertex.spv", "shaders/sign_fragment.spv");
Pipeline sky_pipeline = gen_pipeline("shaders/sky_vertex.spv", "shaders/sky_fragment.spv");
uint32_t attrib_components[3] = {3, 3, 4};
Pipeline block_pipeline = gen_pipeline("shaders/block_vertex.spv", "shaders/block_fragment.spv",
block_uniform, 3, attrib_components, FEATURE_NONE);
attrib_components[2] = 2; // {3, 3, 2}
Pipeline sky_pipeline = gen_pipeline("shaders/sky_vertex.spv", "shaders/sky_fragment.spv",
sky_uniform, 3, attrib_components, FEATURE_NONE);
// {3}
Pipeline line_pipeline = gen_pipeline("shaders/line_vertex.spv", "shaders/line_fragment.spv",
line_uniform, 1, attrib_components, FEATURE_LINE_BIT);
attrib_components[1] = 2; // {3, 2}
Pipeline sign_pipeline = gen_pipeline("shaders/sign_vertex.spv", "shaders/sign_fragment.spv",
sign_uniform, 2, attrib_components, FEATURE_DEPTH_BIAS_BIT);
attrib_components[0] = 2; // {2, 2}
Pipeline text_pipeline = gen_pipeline("shaders/text_vertex.spv", "shaders/text_fragment.spv",
text_uniform, 2, attrib_components, FEATURE_BLEND_BIT);

// CHECK COMMAND LINE ARGUMENTS //
if (argc == 2 || argc == 3) {
Expand Down Expand Up @@ -2635,7 +2649,7 @@ int main(int argc, char **argv) {
glfwGetFramebufferSize(g->window, &g->width, &g->height);
g->scale = get_scale_factor();
Buffer sky_buffer = gen_sky_buffer();
Buffer crosshair_buffer = gen_crosshair_buffer(g->width, g->height, g->scale);
Buffer crosshair_buffer = gen_crosshair_buffer();
Buffer wireframe_buffer = gen_wireframe_buffer(0.53);
Buffer type_buffer = gen_text_buffer(5, MAX_TEXT_LENGTH);
Buffer text_buffer = gen_text_buffer(4, MAX_TEXT_LENGTH * (MAX_MESSAGES + 1) + MAX_INFO_LENGTH + 2 * MAX_NAME_LENGTH);
Expand Down Expand Up @@ -2720,7 +2734,8 @@ int main(int argc, char **argv) {
Player *player = g->players + g->observe1;

// RENDER 3-D SCENE //
clear_frame(CLEAR_COLOR_BIT|CLEAR_DEPTH_BIT);
start_frame();
clear_frame(CLEAR_COLOR_BIT | CLEAR_DEPTH_BIT);
render_sky(sky_pipeline, sky_uniform, player, sky_buffer);
clear_frame(CLEAR_DEPTH_BIT);
int face_count = render_chunks(block_pipeline, block_uniform, player);
Expand All @@ -2729,7 +2744,7 @@ int main(int argc, char **argv) {
render_sign(sign_pipeline, sign_uniform, player, type_len, type_buffer);
render_players(block_pipeline, block_uniform, player);
if (SHOW_WIREFRAME) {
render_wireframe(line_pipeline, line_uniform, player, wireframe_buffer);
render_wireframe(line_pipeline, wire_uniform, player, wireframe_buffer);
}

// RENDER HUD //
Expand All @@ -2738,7 +2753,7 @@ int main(int argc, char **argv) {
render_crosshairs(line_pipeline, line_uniform, crosshair_buffer);
}
if (SHOW_ITEM) {
render_item(block_pipeline, block_uniform, item_buffers[g->item_index]);
render_item(block_pipeline, item_uniform, item_buffers[g->item_index]);
}

// RENDER TEXT //
Expand Down Expand Up @@ -2843,7 +2858,7 @@ int main(int argc, char **argv) {
}

// SWAP AND POLL //
glfwSwapBuffers(g->window);
end_frame(g->window);
glfwPollEvents();
if (glfwWindowShouldClose(g->window)) {
running = 0;
Expand All @@ -2861,27 +2876,39 @@ int main(int argc, char **argv) {
db_disable();
client_stop();
client_disable();
shutdown_renderer();

for(int i = 0; i < item_count; i++)
del_buffer(item_buffers[i]);
free(item_buffers);
del_buffer(type_buffer);
del_buffer(text_buffer);
del_buffer(wireframe_buffer);
del_buffer(crosshair_buffer);
del_buffer(sky_buffer);

del_uniform(block_uniform);
del_uniform(item_uniform);
del_uniform(line_uniform);
del_uniform(wire_uniform);
del_uniform(text_uniform);
del_uniform(sign_uniform);
del_uniform(sky_uniform);

del_pipeline(block_pipeline);
del_pipeline(line_pipeline);
del_pipeline(text_pipeline);
del_pipeline(sign_pipeline);
del_pipeline(sky_pipeline);

del_image(texture);
del_image(font);
del_image(sky);
del_image(sign);
delete_all_chunks();
delete_all_players();
}

glfwTerminate();
del_renderer();
curl_global_cleanup();
return 0;
}
32 changes: 27 additions & 5 deletions src/renderer.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
#ifndef _glrenderer_h_
#define _glrenderer_h_

#include <GLFW/glfw3.h>

// A few bits for efficient communication with renderer
#define CLEAR_COLOR_BIT 0x1
#define CLEAR_DEPTH_BIT 0x2

#define STAGE_VERT_BIT 0x1
#define STAGE_FRAG_BIT 0x2

#define FEATURE_NONE 0x0
#define FEATURE_BLEND_BIT 0x1
#define FEATURE_LINE_BIT 0x2
#define FEATURE_DEPTH_BIAS_BIT 0x4

// Image represents texture and framebuffer image information
// It includes whatever is needed to bind it for rendering
struct ImageObj;
Expand All @@ -22,8 +33,9 @@ typedef struct UniformObj *Uniform;
struct PipelineObj;
typedef struct PipelineObj *Pipeline;

// Initialize persistent global state for the renderer. returns 0 on success
int init_renderer();
// Initialize persistent global state for the renderer using API-dependent <window>
// returns 0 on success
int init_renderer(GLFWwindow *window);
// Clear the full framebuffer for the attachments indicated by the <bitfield>
void clear_frame(uint32_t bitfield);
// Clear the a portion of the framebuffer indicated by <x,y,width,height>
Expand Down Expand Up @@ -72,9 +84,9 @@ void draw_plant(Buffer buffer);
void draw_player(Buffer buffer);
// Draw large sphere around origin represented by vertex buffer <buffer>
void draw_sky(Buffer buffer);
// Create a uniform interface object containing a ubo of size <ubo_size>
// Create a uniform object containing a ubo of size <ubo_size> to be used in <ubo_stages>
// and textures <texture0> and <texture1> then return the handle.
Uniform gen_uniform(uint32_t ubo_size, Image texture0, Image texture1);
Uniform gen_uniform(uint32_t ubo_size, uint32_t ubo_stages, Image texture0, Image texture1);
// Destroy <uniform> and free any associated memory.
void del_uniform(Uniform uniform);
// Load and create a texture image from the file located in <path>
Expand All @@ -83,11 +95,21 @@ Image load_tex_image(const char *path, int linear, int clamp);
// Destroy <image> and free any associated resources
void del_image(Image image);
// Create pipeline object containing shaders as extracted from files <path1> and <path2>
Pipeline gen_pipeline(const char *path1, const char *path2);
// useable with <uniform> with <attrib_ct> attribs containing <components> enabling <feature_bits>
Pipeline gen_pipeline(const char *path1, const char *path2, Uniform uniform,
uint32_t attrib_ct, const uint32_t *components, uint32_t feature_bits);
// Bind the pipeline and <uniform> interfaces for rendering
// and init ubo with <size> bytes of <data>
void bind_pipeline(Pipeline pipeline, Uniform uniform, int size, void *data);
// Destroy pipeline object <pipeline> and free any associated memory
void del_pipeline(Pipeline pipeline);
// Perform any initialization or setup required at the start of the frame rendering
void start_frame();
// Perform any shutdown or submission required at the end of the frame rendering to <window>
void end_frame(GLFWwindow *window);
// Conclude any rendering by the renderer in preparation for deletion
void shutdown_renderer();
// Destroy all renderer resources and free any memory
void del_renderer();

#endif // _glrenderer_
Loading

0 comments on commit 33224b1

Please sign in to comment.