minecraft: initial

This commit is contained in:
Zack Buhman 2026-04-27 22:55:07 -05:00
parent 9f636548db
commit 759006dd2f
13 changed files with 34754 additions and 3 deletions

View File

@ -25,7 +25,7 @@ CFLAGS += -I./data
CFLAGS += -I../SDL3-dist/include CFLAGS += -I../SDL3-dist/include
CFLAGS += -fpic CFLAGS += -fpic
FLAGS += -fstack-protector -fstack-protector-all -fno-omit-frame-pointer -fsanitize=address #FLAGS += -fstack-protector -fstack-protector-all -fno-omit-frame-pointer -fsanitize=address
LDFLAGS += -lm LDFLAGS += -lm
ifeq ($(UNAME),Linux) ifeq ($(UNAME),Linux)
@ -48,7 +48,14 @@ OBJS = \
src/collada/scene/reload.o \ src/collada/scene/reload.o \
src/collada/scene.o \ src/collada/scene.o \
src/collada/node_state.o \ src/collada/node_state.o \
src/collada/animate.o src/collada/animate.o \
src/minecraft/world.o \
src/minecraft/entry_table.o \
src/minecraft/vulkan.o
WORLDS = \
data/minecraft/midnightmeadow/inthash.o \
data/minecraft/grandlecturn/inthash.o
SCENES = \ SCENES = \
data/scenes/shadow_test/shadow_test.o \ data/scenes/shadow_test/shadow_test.o \
@ -73,7 +80,7 @@ all: main
%.o: %.s %.o: %.s
$(AS) $< -o $@ $(AS) $< -o $@
main: $(OBJS) $(LIBS) $(SCENES) main: $(OBJS) $(LIBS) $(SCENES) $(WORLDS)
$(CC) $(ARCH) $(LDFLAGS) $(FLAGS) $(OPT) $(DEBUG) $^ -o $@ $(CC) $(ARCH) $(LDFLAGS) $(FLAGS) $(OPT) $(DEBUG) $^ -o $@
%.spv: %.hlsl %.spv: %.hlsl

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <stdint.h>
namespace file { namespace file {
void const * open(char const * filename, uint32_t * out_size); void const * open(char const * filename, uint32_t * out_size);

View File

@ -0,0 +1,27 @@
#pragma once
#include <stdint.h>
#include <assert.h>
namespace minecraft::entry_table {
typedef struct global_entry {
int global_index;
uint8_t block_id;
uint8_t block_data;
uint16_t _padding;
} global_entry_t;
static_assert((sizeof (global_entry_t)) == 8);
typedef uint32_t (hash_func_t)(const int32_t key);
void load_entry_table(char const * const path,
global_entry_t ** out_entry_table,
int * out_entry_table_length,
hash_func_t * hash_func);
global_entry_t * const world_lookup(hash_func_t * hash_func,
global_entry_t * entry_table,
int entry_table_length,
int x, int y, int z);
}

View File

@ -0,0 +1,14 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
uint32_t grandlecturn_hash(const int32_t key);
uint32_t midnightmeadow_hash(const int32_t key);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,41 @@
#pragma once
#include "volk/volk.h"
namespace minecraft {
struct vulkan {
struct {
VkDeviceSize jointWeightOffset;
VkDeviceSize indexOffset;
VkBuffer buffer;
VkDeviceMemory memory;
} vertexIndex;
// externally initialized, opaque handle
VkInstance instance;
VkDevice device;
VkQueue queue;
VkCommandPool commandPool;
// externally initialized, structures
VkPhysicalDeviceProperties physicalDeviceProperties;
VkPhysicalDeviceMemoryProperties physicalDeviceMemoryProperties;
// externally initialized, enum
VkFormat colorFormat;
VkFormat depthFormat;
//
// method initialized
//
VkPipelineLayout pipelineLayout;
VkShaderModule shaderModule;
VkPipeline pipeline;
void init();
void load_vertex_index_buffer(char const * vertex_filename,
char const * index_filename);
void load_shader();
void create_pipeline();
void draw();
};
}

53
include/minecraft/world.h Normal file
View File

@ -0,0 +1,53 @@
#pragma once
#include <stdint.h>
#include "minecraft/entry_table.h"
namespace minecraft::world {
struct vtx_cfg {
const char * vtx;
const char * cfg;
};
struct descriptor {
int const region_count;
vtx_cfg const * const vertex_paths;
char const * const entry_table_path;
char const * const lights_path;
entry_table::hash_func_t * hash_func;
};
struct world_id {
enum {
GRANDLECTURN = 0,
MIDNIGHTMEADOW = 1,
};
};
// also update index_buffer_custom_offsets in include/minecraft_data.inc
const int custom_block_types = 7;
const int instance_cfg_length = 64 + custom_block_types;
struct instance_cfg_entry {
int count;
int offset;
};
struct region {
unsigned int per_instance_vertex_buffer;
instance_cfg_entry instance_cfg[instance_cfg_length];
};
struct state {
world::descriptor const * descriptor;
world::region * region; // malloc region_count
entry_table::global_entry_t * entry_table;
unsigned int light_uniform_buffer;
int light_count;
int entry_table_length;
};
extern descriptor const descriptors[];
extern int const descriptors_length;
}

View File

@ -0,0 +1,64 @@
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "file.h"
#include "new.h"
#include "minecraft/entry_table.h"
namespace minecraft::entry_table {
void load_entry_table(char const * const path,
global_entry_t ** out_entry_table,
int * out_entry_table_length,
hash_func_t * hash_func)
{
uint32_t global_size;
global_entry_t const * entry = (global_entry_t const *)file::open(path, &global_size);
assert(entry != NULL);
int entry_table_length = global_size / (sizeof (global_entry_t));
global_entry_t * entry_table = NewM<global_entry_t>(entry_table_length);
memset(entry_table, 0, global_size);
for (int i = 0; i < entry_table_length; i++) {
uint32_t ix = hash_func(entry[i].global_index);
if (entry_table[ix].global_index != 0) {
printf("collision hash(%d) = %d ; %d\n", entry[i].global_index, ix, entry_table[ix].global_index);
assert(hash_func(entry_table[ix].global_index) == ix);
}
//assert(entry_table[ix].global_index == 0);
memcpy(&entry_table[ix], &entry[i], (sizeof (global_entry_t)));
}
*out_entry_table = entry_table;
*out_entry_table_length = entry_table_length;
}
static inline int global_index_from_xyz(int x, int y, int z)
{
const int g_stride = 512 * 2;
if (x < 0)
x = -(x - 511);
if (z < 0)
z = -(z - 511);
return x + z * g_stride + y * g_stride * g_stride;
}
global_entry_t * const world_lookup(hash_func_t * hash_func,
global_entry_t * entry_table,
int entry_table_length,
int x, int y, int z)
{
int global_index = global_index_from_xyz(x, y, z);
int table_index = hash_func(global_index);
if (table_index < 0 || table_index >= entry_table_length)
return NULL;
global_entry_t * const entry = &entry_table[table_index];
if (entry->global_index != global_index)
return NULL;
return entry;
}
}

313
src/minecraft/vulkan.cpp Normal file
View File

@ -0,0 +1,313 @@
#include <stdio.h>
#include <string.h>
#include "volk/volk.h"
#include "vulkan/vk_enum_string_helper.h"
#include "file.h"
#include "check.h"
#include "vulkan_helper.h"
#include "minecraft/vulkan.h"
namespace minecraft {
void vulkan::init()
{
load_vertex_index_buffer("data/minecraft/per_vertex.vtx", "data/minecraft/configuration.idx");
load_shader();
create_pipeline();
}
//////////////////////////////////////////////////////////////////////
// vertex index buffer
//////////////////////////////////////////////////////////////////////
void vulkan::load_vertex_index_buffer(char const * vertex_filename,
char const * index_filename)
{
uint32_t vertexSize;
void const * vertexStart = file::open(vertex_filename, &vertexSize);
uint32_t indexSize;
void const * indexStart = file::open(index_filename, &indexSize);
vertexIndex.indexOffset = vertexSize;
// create buffer
VkDeviceSize bufferSize{ vertexSize + indexSize };
VkBufferCreateInfo vertexIndexBufferCreateInfo{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = bufferSize,
.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE
};
VK_CHECK(vkCreateBuffer(device, &vertexIndexBufferCreateInfo, nullptr, &vertexIndex.buffer));
// allocate memory
VkMemoryRequirements memoryRequirements;
vkGetBufferMemoryRequirements(device, vertexIndex.buffer, &memoryRequirements);
VkMemoryPropertyFlags memoryPropertyFlags{ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT };
VkMemoryAllocateFlags memoryAllocateFlags{};
VkDeviceSize stride;
allocateFromMemoryRequirements(device,
physicalDeviceProperties.limits.nonCoherentAtomSize,
physicalDeviceMemoryProperties,
memoryRequirements,
memoryPropertyFlags,
memoryAllocateFlags,
1,
&vertexIndex.memory,
&stride);
VK_CHECK(vkBindBufferMemory(device, vertexIndex.buffer, vertexIndex.memory, 0));
// copy data
void * vertexIndexMappedData;
VK_CHECK(vkMapMemory(device, vertexIndex.memory, 0, VK_WHOLE_SIZE, 0, &vertexIndexMappedData));
memcpy((void *)(((ptrdiff_t)vertexIndexMappedData) + 0), vertexStart, vertexSize);
memcpy((void *)(((ptrdiff_t)vertexIndexMappedData) + vertexSize), indexStart, indexSize);
VkMappedMemoryRange mappedMemoryRange{
.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
.memory = vertexIndex.memory,
.offset = 0,
.size = VK_WHOLE_SIZE,
};
vkFlushMappedMemoryRanges(device, 1, &mappedMemoryRange);
vkUnmapMemory(device, vertexIndex.memory);
}
//////////////////////////////////////////////////////////////////////
// shader
//////////////////////////////////////////////////////////////////////
void vulkan::load_shader()
{
uint32_t shaderSize;
void const * shaderStart = file::open("shader/minecraft.spv", &shaderSize);
VkShaderModuleCreateInfo shaderModuleCreateInfo{
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = shaderSize,
.pCode = (uint32_t *)shaderStart
};
VK_CHECK(vkCreateShaderModule(device, &shaderModuleCreateInfo, nullptr, &shaderModule));
}
//////////////////////////////////////////////////////////////////////
// pipeline
//////////////////////////////////////////////////////////////////////
void vulkan::create_pipeline()
{
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
//.setLayoutCount = 2,
//.pSetLayouts = descriptorSetLayouts,
//.pushConstantRangeCount = 0,
//.pPushConstantRanges = nullptr
};
VK_CHECK(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState{
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST
};
VkPipelineShaderStageCreateInfo shaderStages[2]{
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_VERTEX_BIT,
.module = shaderModule,
.pName = "VSMain"
},
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
.module = shaderModule,
.pName = "PSMain"
}
};
VkPipelineViewportStateCreateInfo viewportState{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.viewportCount = 1,
.scissorCount = 1
};
constexpr uint32_t dynamicStateCount = 2;
VkDynamicState dynamicStates[dynamicStateCount]{
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR,
};
VkPipelineDynamicStateCreateInfo dynamicState{
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.dynamicStateCount = dynamicStateCount,
.pDynamicStates = dynamicStates
};
VkPipelineDepthStencilStateCreateInfo depthStencilState{
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
.depthTestEnable = VK_TRUE,
.depthWriteEnable = VK_TRUE,
.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL,
.stencilTestEnable = VK_TRUE,
.front = {
.failOp = VK_STENCIL_OP_REPLACE,
.passOp = VK_STENCIL_OP_REPLACE,
.depthFailOp = VK_STENCIL_OP_REPLACE,
.compareOp = VK_COMPARE_OP_ALWAYS,
.compareMask = 0x01,
.writeMask = 0x01,
.reference = 1,
},
.back = {
.failOp = VK_STENCIL_OP_REPLACE,
.passOp = VK_STENCIL_OP_REPLACE,
.depthFailOp = VK_STENCIL_OP_REPLACE,
.compareOp = VK_COMPARE_OP_ALWAYS,
.compareMask = 0x01,
.writeMask = 0x01,
.reference = 1,
},
};
VkPipelineRenderingCreateInfo renderingCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
.colorAttachmentCount = 1,
.pColorAttachmentFormats = &colorFormat,
.depthAttachmentFormat = depthFormat,
.stencilAttachmentFormat = depthFormat
};
VkPipelineColorBlendAttachmentState blendAttachment{
.colorWriteMask = 0xF
};
VkPipelineColorBlendStateCreateInfo colorBlendState{
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
.attachmentCount = 1,
.pAttachments = &blendAttachment
};
VkPipelineRasterizationStateCreateInfo rasterizationState{
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
//.cullMode = VK_CULL_MODE_BACK_BIT,
.cullMode = VK_CULL_MODE_NONE,
.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
.lineWidth = 1.0f
};
VkPipelineMultisampleStateCreateInfo multisampleState{
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
};
constexpr int perVertexSize = (3 + 3 + 2) * 2;
constexpr int perInstanceSize = (3 + 1 + 3 + 1) * 2;
VkVertexInputBindingDescription vertexBindingDescriptions[2]{
{
.binding = 0,
.stride = perVertexSize,
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX
},
{
.binding = 1,
.stride = perInstanceSize,
.inputRate = VK_VERTEX_INPUT_RATE_INSTANCE
}
};
VkVertexInputAttributeDescription vertexAttributeDescriptions[8]{
// per-vertex
{ // position
.location = 0,
.binding = 0,
.format = VK_FORMAT_R16G16B16_SFLOAT,
.offset = 0,
},
{ // normal
.location = 1,
.binding = 0,
.format = VK_FORMAT_R16G16B16_SFLOAT,
.offset = 6,
},
{ // texture
.location = 2,
.binding = 0,
.format = VK_FORMAT_R16G16_SFLOAT,
.offset = 12,
},
// per-instance
{ // block position
.location = 3,
.binding = 1,
.format = VK_FORMAT_R16G16B16_SINT,
.offset = 0,
},
{ // block id
.location = 4,
.binding = 1,
.format = VK_FORMAT_R16_SINT,
.offset = 8,
},
{ // block id
.location = 5,
.binding = 1,
.format = VK_FORMAT_R16_SINT,
.offset = 10,
},
{ // block id
.location = 6,
.binding = 1,
.format = VK_FORMAT_R16_SINT,
.offset = 12,
},
{ // block id
.location = 7,
.binding = 1,
.format = VK_FORMAT_R16_SINT,
.offset = 14,
},
};
VkPipelineVertexInputStateCreateInfo vertexInputState{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.vertexBindingDescriptionCount = 2,
.pVertexBindingDescriptions = vertexBindingDescriptions,
.vertexAttributeDescriptionCount = 8,
.pVertexAttributeDescriptions = vertexAttributeDescriptions,
};
VkGraphicsPipelineCreateInfo pipelineCreateInfos[1]{
{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = &renderingCreateInfo,
.stageCount = 2,
.pStages = shaderStages,
.pVertexInputState = &vertexInputState,
.pInputAssemblyState = &inputAssemblyState,
.pViewportState = &viewportState,
.pRasterizationState = &rasterizationState,
.pMultisampleState = &multisampleState,
.pDepthStencilState = &depthStencilState,
.pColorBlendState = &colorBlendState,
.pDynamicState = &dynamicState,
.layout = pipelineLayout
},
};
VK_CHECK(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, pipelineCreateInfos, nullptr, &pipeline));
}
//////////////////////////////////////////////////////////////////////
// draw
//////////////////////////////////////////////////////////////////////
void vulkan::draw()
{
}
}

38
src/minecraft/world.cpp Normal file
View File

@ -0,0 +1,38 @@
#include "minecraft/world.h"
#include "minecraft/inthash.h"
namespace minecraft::world {
static vtx_cfg const grandlecturn_vertex_paths[] = {
{ "minecraft/grandlecturn/region.0.0.instance.vtx", "minecraft/grandlecturn/region.0.0.instance.cfg" },
{ "minecraft/grandlecturn/region.-1.0.instance.vtx", "minecraft/grandlecturn/region.-1.0.instance.cfg" },
{ "minecraft/grandlecturn/region.0.-1.instance.vtx", "minecraft/grandlecturn/region.0.-1.instance.cfg" },
{ "minecraft/grandlecturn/region.-1.-1.instance.vtx", "minecraft/grandlecturn/region.-1.-1.instance.cfg" },
};
static vtx_cfg const midnightmeadow_vertex_paths[] = {
{ "minecraft/midnightmeadow/region.0.0.instance.vtx", "minecraft/midnightmeadow/region.0.0.instance.cfg" },
{ "minecraft/midnightmeadow/region.-1.0.instance.vtx", "minecraft/midnightmeadow/region.-1.0.instance.cfg" },
{ "minecraft/midnightmeadow/region.0.-1.instance.vtx", "minecraft/midnightmeadow/region.0.-1.instance.cfg" },
{ "minecraft/midnightmeadow/region.-1.-1.instance.vtx", "minecraft/midnightmeadow/region.-1.-1.instance.cfg" },
};
descriptor const descriptors[] = {
//[world_id::GRANDLECTURN] =
{
.region_count = 4,
.vertex_paths = grandlecturn_vertex_paths,
.entry_table_path = "minecraft/grandlecturn/global.dump",
.lights_path = "minecraft/grandlecturn/global.lights.vtx",
.hash_func = grandlecturn_hash,
},
//[world_id::MIDNIGHTMEADOW] =
{
.region_count = 4,
.vertex_paths = midnightmeadow_vertex_paths,
.entry_table_path = "minecraft/midnightmeadow/global.dump",
.lights_path = "minecraft/midnightmeadow/global.lights.vtx",
.hash_func = midnightmeadow_hash,
},
};
int const descriptors_length = (sizeof (descriptors)) / (sizeof (descriptors[0]));
}