From af94aeeef70eb9f00edd431afb90008a650f0f1f Mon Sep 17 00:00:00 2001 From: Andreev Gregory Date: Wed, 30 Jul 2025 14:43:38 +0300 Subject: [PATCH] Generating a f***ed up normal texture --- .gitignore | 5 +- src/l1/core/util.h | 4 + src/l1/gen/geom_and_textures.h | 62 ++- src/l2/margaret/margaret.h | 17 +- src/l2/marie/graphics_geom.h | 9 + src/l2/tests/r0.c | 93 ++-- src/l2/tests/r0_assets.h | 419 +++++++++++++----- src/l2/tests/r0_tex_init_prep.c | 36 +- src/l2/tests/test_shaders/glsl/0/0.frag | 3 +- .../tests/test_textures/bitmap_converter.py | 54 ++- .../test_textures/log_10_2_6_TEMPLATE.png | Bin 1084 -> 0 bytes src/l2/tests/test_textures/log_10_2_6_v2.png | Bin 18647 -> 0 bytes 12 files changed, 512 insertions(+), 190 deletions(-) delete mode 100644 src/l2/tests/test_textures/log_10_2_6_TEMPLATE.png delete mode 100644 src/l2/tests/test_textures/log_10_2_6_v2.png diff --git a/.gitignore b/.gitignore index 4306bd3..1fa5686 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ vgcore.* /gen/ *.pdf *.r8g8b8a8 -*.r8b8g8 +*.r8g8b8 *.r8 +*.a8 *.xcf +*_NORMAL.png +*_TEMPLATE.png \ No newline at end of file diff --git a/src/l1/core/util.h b/src/l1/core/util.h index 383d54d..886cd44 100644 --- a/src/l1/core/util.h +++ b/src/l1/core/util.h @@ -310,4 +310,8 @@ T OptionT##_expect(OptionT self){ \ #define OptionT_struct_Definition(T) OptionT_struct_Definition_custom_name(T, Option##T) #define OptionT_method_Definition(T) OptionT_method_Definition_custom_name(T, Option##T) +float pow2f(float x) { + return x * x; +} + #endif diff --git a/src/l1/gen/geom_and_textures.h b/src/l1/gen/geom_and_textures.h index 18a7082..a4c036c 100644 --- a/src/l1/gen/geom_and_textures.h +++ b/src/l1/gen/geom_and_textures.h @@ -214,6 +214,43 @@ NODISCARD VecU8 generate_xvecy_method_and_one(ConstSpanU8 xvec, int n) { return res; } +NODISCARD VecU8 generate_xvecy_method_dot(ConstSpanU8 xvec, ConstSpanU8 member, int n) { + VecU8 res = VecU8_from_span(member); + VecU8_append(&res, ' '); + string_append_xvecy(&res, xvec, n); + VecU8_append_span(&res, cstr("_dot(")); + string_append_xvecy(&res, xvec, n); + VecU8_append_span(&res, cstr(" A, ")); + string_append_xvecy(&res, xvec, n); + VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return ")); + for (int i = 0; i < n; i++) { + if (i) + VecU8_append_span(&res, cstr(" + ")); + VecU8_append_span(&res, cstr("A.")); + string_append_vec_field_name(&res, i); + VecU8_append_span(&res, cstr(" * B.")); + string_append_vec_field_name(&res, i); + } + VecU8_append_span(&res, cstr(";\n}\n\n")); + return res; +} + +NODISCARD VecU8 generate_xvec3_method_cross(ConstSpanU8 xvec) { + VecU8 res = VecU8_new(); + string_append_xvecy(&res, xvec, 3); + VecU8_append(&res, ' '); + string_append_xvecy(&res, xvec, 3); + VecU8_append_span(&res, cstr("_cross(")); + string_append_xvecy(&res, xvec, 3); + VecU8_append_span(&res, cstr(" A, ")); + string_append_xvecy(&res, xvec, 3); + VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return (")); + string_append_xvecy(&res, xvec, 3); + VecU8_append_span(&res, cstr("){A.y * B.z - A.z * B.y, -A.x * B.z + A.z * B.x, A.x * B.y - A.y * B.x};\n}\n\n")); + return res; +} + + void string_append_xmatnm(VecU8* str, ConstSpanU8 xmat, int cols, int rows) { VecU8_append_span(str, xmat); VecU8_append(str, '0' + cols); @@ -439,29 +476,6 @@ NODISCARD VecU8 generate_xmatnm_method_div_by_scal(ConstSpanU8 xmat, ConstSpanU8 return res; } -NODISCARD VecU8 generate_xvecn_method_dot_xvecn(ConstSpanU8 xvec, ConstSpanU8 member, int n) { - VecU8 res = VecU8_from_span(member); - VecU8_append(&res, ' '); - string_append_xvecy(&res, xvec, n); - VecU8_append_span(&res, cstr("_dot_")); - string_append_xvecy(&res, xvec, n); - VecU8_append(&res, '('); - string_append_xvecy(&res, xvec, n); - VecU8_append_span(&res, cstr(" A, ")); - string_append_xvecy(&res, xvec, n); - VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return ")); - for (int i = 0; i < n; i++) { - if (i) - VecU8_append_span(&res, cstr(" + ")); - VecU8_append_span(&res, cstr("A.")); - string_append_vec_field_name(&res, i); - VecU8_append_span(&res, cstr(" * B.")); - string_append_vec_field_name(&res, i); - } - VecU8_append_span(&res, cstr(";\n}\n\n")); - return res; -} - NODISCARD VecU8 generate_xmatnm_method_mul_xvecn(ConstSpanU8 xmat, ConstSpanU8 xvec, int n, int m) { VecU8 res = VecU8_new(); string_append_xvecy(&res, xvec, m); @@ -575,9 +589,11 @@ NODISCARD VecU8 generate_xvec234_structs_and_methods(ConstSpanU8 xvec, ConstSpan VecU8_append_vec(&res, generate_xvecy_method_mul_scal(xvec, member, cc)); VecU8_append_vec(&res, generate_xvecy_method_div_by_scal(xvec, member, cc)); VecU8_append_vec(&res, generate_xvecy_method_mul_xvecy(xvec, member, cc)); + VecU8_append_vec(&res, generate_xvecy_method_dot(xvec, member, cc)); } for (int n = 2; n <= 3; n++) VecU8_append_vec(&res, generate_xvecy_method_and_one(xvec, n)); + VecU8_append_vec(&res, generate_xvec3_method_cross(xvec)); return res; } diff --git a/src/l2/margaret/margaret.h b/src/l2/margaret/margaret.h index a274929..2069a1c 100644 --- a/src/l2/margaret/margaret.h +++ b/src/l2/margaret/margaret.h @@ -848,7 +848,7 @@ VkSwapchainKHR MargaretSwapchainBundle_pop_swapchain_drop_rest(VkDevice device, // Not a regular _drop method, because it requires a bundled VkDevice void MargaretSwapchainBundle_drop_with_device(VkDevice device, MargaretSwapchainBundle swfb) { VkSwapchainKHR swapchain = MargaretSwapchainBundle_pop_swapchain_drop_rest(device, swfb); - vkDestroySwapchainKHR(device, swfb.swapchain, NULL); + vkDestroySwapchainKHR(device, swapchain, NULL); // Now swapchain bundle is 100% dropped } @@ -1114,11 +1114,16 @@ MargaretBufferInMemoryInfo margaret_prep_buffer_mem_info_of_small_local_ubo(size return (MargaretBufferInMemoryInfo){ .sz = struct_sz, .usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT }; } -MargaretImageInMemoryInfo margaret_prep_image_mem_info_of_gpu_texture_rgba(uint32_t w, uint32_t h) { +MargaretImageInMemoryInfo margaret_prep_image_mem_info_of_gpu_texture_srgba(uint32_t w, uint32_t h) { return (MargaretImageInMemoryInfo){ .width = w, .height = h, .format = VK_FORMAT_R8G8B8A8_SRGB, .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT }; } +MargaretImageInMemoryInfo margaret_prep_image_mem_info_of_gpu_texture_unorm_32(uint32_t w, uint32_t h) { + return (MargaretImageInMemoryInfo){ .width = w, .height = h, .format = VK_FORMAT_R8G8B8A8_UNORM, + .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT }; +} + MargaretImageInMemoryInfo margaret_prep_image_mem_info_of_zbuffer(uint32_t max_width, uint32_t max_height, VkFormat zbuf_format) { return (MargaretImageInMemoryInfo){ .width = max_width, .height = max_height, .format = zbuf_format, .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT }; @@ -1303,16 +1308,16 @@ VkImageView margaret_create_view_for_image ( } // For texture -VkSampler margaret_create_sampler(VkPhysicalDevice physical_device, VkDevice device) { +VkSampler margaret_create_sampler(VkPhysicalDevice physical_device, VkDevice device, bool make_linear) { VkPhysicalDeviceProperties physical_device_properties; vkGetPhysicalDeviceProperties(physical_device, &physical_device_properties); VkPhysicalDeviceFeatures physical_device_features; vkGetPhysicalDeviceFeatures(physical_device, &physical_device_features); VkSamplerCreateInfo crinfo = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, - .magFilter = VK_FILTER_LINEAR, - .minFilter = VK_FILTER_LINEAR, - .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, + .magFilter = make_linear ? VK_FILTER_LINEAR : VK_FILTER_NEAREST, + .minFilter = make_linear ? VK_FILTER_LINEAR : VK_FILTER_NEAREST, + .mipmapMode = make_linear? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST, .addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, .addressModeV = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, .addressModeW = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, diff --git a/src/l2/marie/graphics_geom.h b/src/l2/marie/graphics_geom.h index 36fcd00..5711d7a 100644 --- a/src/l2/marie/graphics_geom.h +++ b/src/l2/marie/graphics_geom.h @@ -74,4 +74,13 @@ mat3 marie_simple_camera_rot_m_basis_in_cols(float yaw, float pitch, float roll) }; } +vec2 marie_trigonom_circle(float angle) { + return (vec2){cosf(angle), sinf(angle)}; +} + +vec3 marie_normal_from_tang_space_gradient(float delt_x, float delta_z) { + float N = 1 / sqrtf(delt_x * delt_x + delta_z * delta_z + 1); + return (vec3){-delt_x * N, N, -delta_z * N}; +} + #endif diff --git a/src/l2/tests/r0.c b/src/l2/tests/r0.c index 87c052e..4c867bb 100644 --- a/src/l2/tests/r0.c +++ b/src/l2/tests/r0.c @@ -205,7 +205,6 @@ PipelineHands create_graphics_pipeline_0( VkDescriptorSetLayoutBinding bindings_for_my_descr_set_layout[] = { { - // Binding in shader .binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, // our shader variable is not an array of descriptors, so this stays 1 @@ -218,6 +217,12 @@ PipelineHands create_graphics_pipeline_0( .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, }, + { + .binding = 2, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + }, }; VkDescriptorSetLayoutCreateInfo descriptor_set_layout_crinfo = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, @@ -715,14 +720,19 @@ int main() { ModelTopology cylinder_1 = generate_one_fourth_of_a_cylinder(10, 2, 6); ModelTopology cylinder_2 = generate_one_fourth_of_a_cylinder(5, 5, 10); // TextureDataR8G8B8A8 wood_texture_data = generate_texture_for_one_fourth_of_a_cylinder(20, 10, 2, 6); - TextureDataR8G8B8A8 wood_texture_data = TextureDataR8G8B8A8_read_from_file("test_textures/log_10_2_6.r8g8b8a8"); + // todo: learn how to use libpng + TextureDataR8G8B8A8 cyl_1_diffuse_tex = TextureDataR8G8B8A8_read_from_file("test_textures/log_10_2_6.r8g8b8a8"); + // todo: learn how to write texture immediately from file to mapped host memory buffer + // todo: and at the same time I need to add methods to convert between these formats + TextureDataR8G8B8A8 cyl_1_normal_tex = TextureDataR8G8B8A8_read_from_file("test_textures/log_10_2_6_NORMAL.r8g8b8a8"); // We have only one staging buffer in host memory (because we don't really need more) MargaretBufferInMemoryInfo host_mem_buffer = (MargaretBufferInMemoryInfo){ .sz = MAX_U64(ModelTopology_get_space_needed_for_staging_buffer(&cylinder_1), MAX_U64(ModelTopology_get_space_needed_for_staging_buffer(&cylinder_2), MAX_U64(sizeof(Pipeline0UBO), - MAX_U64(TextureDataR8G8B8A8_get_size_in_bytes(&wood_texture_data), 0)))) + MAX_U64(TextureDataR8G8B8A8_get_size_in_bytes(&cyl_1_diffuse_tex), + MAX_U64(TextureDataR8G8B8A8_get_size_in_bytes(&cyl_1_normal_tex), 0))))) , .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT }; VkDeviceMemory host_mem = margaret_initialize_buffers_and_images(physical_device, device, (SpanMargaretBufferInMemoryInfo){.data = &host_mem_buffer, .len = 1}, (SpanMargaretImageInMemoryInfo){ 0 }, @@ -736,11 +746,14 @@ int main() { margaret_prep_buffer_mem_info_of_gpu_ubo(sizeof(Pipeline0UBO)), }; MargaretImageInMemoryInfo device_mem_images[] = { - margaret_prep_image_mem_info_of_gpu_texture_rgba(wood_texture_data.width, - TextureDataR8G8B8A8_get_height(&wood_texture_data)), margaret_prep_image_mem_info_of_colorbuffer(MAX_WIN_WIDTH, MAX_WIN_HEIGHT, IT1_format.some), margaret_prep_image_mem_info_of_zbuffer(MAX_WIN_WIDTH, MAX_WIN_HEIGHT, zbuffer_format.some), + margaret_prep_image_mem_info_of_gpu_texture_srgba(cyl_1_diffuse_tex.width, + TextureDataR8G8B8A8_get_height(&cyl_1_diffuse_tex)), + margaret_prep_image_mem_info_of_gpu_texture_srgba(cyl_1_normal_tex.width, + TextureDataR8G8B8A8_get_height(&cyl_1_normal_tex)), }; + // todo: replace this ugly garbage with pointer spans. Seriously, this is fucking digusting VkDeviceMemory device_mem = margaret_initialize_buffers_and_images(physical_device, device, (SpanMargaretBufferInMemoryInfo){ .data = device_mem_buffers, .len = ARRAY_SIZE(device_mem_buffers)}, (SpanMargaretImageInMemoryInfo){ .data = device_mem_images, .len = ARRAY_SIZE(device_mem_images) }, @@ -750,9 +763,10 @@ int main() { MargaretBufferInMemoryInfo device_vbo_2_buffer = device_mem_buffers[2]; MargaretBufferInMemoryInfo device_ebo_2_buffer = device_mem_buffers[3]; MargaretBufferInMemoryInfo device_ubo_my_buffer = device_mem_buffers[4]; - MargaretImageInMemoryInfo device_wood_texture = device_mem_images[0]; - MargaretImageInMemoryInfo device_IT1_image = device_mem_images[1]; - MargaretImageInMemoryInfo device_zbuffer_image = device_mem_images[2]; + MargaretImageInMemoryInfo device_IT1_image = device_mem_images[0]; + MargaretImageInMemoryInfo device_zbuffer_image = device_mem_images[1]; + MargaretImageInMemoryInfo device_cyl_1_diffuse_texture = device_mem_images[2]; + MargaretImageInMemoryInfo device_cyl_1_normal_texture = device_mem_images[3]; VkCommandPool command_pool = margaret_create_resettable_command_pool(device, queue_fam.for_graphics); VkCommandBuffer rendering_command_buffer_0 = margaret_allocate_command_buffer(device, command_pool); @@ -790,14 +804,20 @@ int main() { margaret_copy_buffer_imm(device, command_pool, graphics_queue, device_ebo_2_buffer.buffer, host_mem_buffer.buffer, size); } - memcpy(host_mem_buffer_mem, wood_texture_data.pixels.buf, - TextureDataR8G8B8A8_get_size_in_bytes(&wood_texture_data)); - margaret_copy_buffer_to_texture_for_frag_shader_imm(device, command_pool, graphics_queue, - &device_wood_texture, host_mem_buffer.buffer); + { + memcpy(host_mem_buffer_mem, cyl_1_diffuse_tex.pixels.buf, + TextureDataR8G8B8A8_get_size_in_bytes(&cyl_1_diffuse_tex)); + margaret_copy_buffer_to_texture_for_frag_shader_imm(device, command_pool, graphics_queue, + &device_cyl_1_diffuse_texture, host_mem_buffer.buffer); + } + { + memcpy(host_mem_buffer_mem, cyl_1_normal_tex.pixels.buf, TextureDataR8G8B8A8_get_size_in_bytes(&cyl_1_normal_tex)); + margaret_copy_buffer_to_texture_for_frag_shader_imm(device, command_pool, graphics_queue, + &device_cyl_1_normal_texture, host_mem_buffer.buffer); + } + // We sent everything we needed. but host_mem_buffer_mem may be used later - // My wood texture needs VkImageView - VkImageView wood_texture_view = margaret_create_view_for_image(device, &device_wood_texture, VK_IMAGE_ASPECT_COLOR_BIT); // My zbuffer also needs a view VkImageView zbuffer_view = margaret_create_view_for_image(device, &device_zbuffer_image, VK_IMAGE_ASPECT_DEPTH_BIT); /* Here we create an image view into a temporary IT1 texture and a framebuffer for scene rendering */ @@ -805,17 +825,23 @@ int main() { VkFramebuffer IT1_framebuffer = create_IT1_framebuffer(device, IT1_view, zbuffer_view, render_pass_0, MAX_WIN_WIDTH, MAX_WIN_HEIGHT); + // My cylinder 1 texture needs VkImageView + VkImageView cyl_1_diffuse_texture_view = margaret_create_view_for_image(device, &device_cyl_1_diffuse_texture, VK_IMAGE_ASPECT_COLOR_BIT); + // My cylinder 1 normal texture also needs NkImageView + VkImageView cyl_1_normal_texture_view = margaret_create_view_for_image(device, &device_cyl_1_normal_texture, VK_IMAGE_ASPECT_COLOR_BIT); + Scene scene = Scene_new(); VecUsedModelOnScene_append(&scene.models, (UsedModelOnScene){.model = { .vbo = device_vbo_1_buffer.buffer, .ebo = device_ebo_1_buffer.buffer, .indexes = cylinder_1.indexes.len }, .model_t = marie_translation_mat4((vec3){1, -1, 5}) }); VecUsedModelOnScene_append(&scene.models, (UsedModelOnScene){.model = { .vbo = device_vbo_2_buffer.buffer, .ebo = device_ebo_2_buffer.buffer, .indexes = cylinder_2.indexes.len - }, .model_t = marie_translation_mat4((vec3){6, -3, 7}) }); + }, .model_t = marie_translation_mat4((vec3){6, -3, 16}) }); - // Sampler is global for a lot of my future textures - VkSampler my_texture_sampler = margaret_create_sampler(physical_device, device); - VkDescriptorPool descriptor_pool = margaret_create_descriptor_set_pool(device, 1, 2, 2); + // These samplers are global for a lot of my future textures + VkSampler linear_sampler = margaret_create_sampler(physical_device, device, true); + VkSampler nearest_sampler = margaret_create_sampler(physical_device, device, false); + VkDescriptorPool descriptor_pool = margaret_create_descriptor_set_pool(device, 1, 3, 2); VkDescriptorSet descriptor_set_for_pipeline_0 = margaret_allocate_descriptor_set(device, descriptor_pool, pipeline_hands_0.descriptor_set_layout); VkDescriptorSet descriptor_set_for_pipeline_1 = margaret_allocate_descriptor_set(device, descriptor_pool, pipeline_hands_1.descriptor_set_layout); @@ -827,12 +853,17 @@ int main() { .range = sizeof(Pipeline0UBO), }; VkDescriptorImageInfo image_info_for_descriptor_1_in_set_0 = { - .sampler = my_texture_sampler, - .imageView = wood_texture_view, + .sampler = linear_sampler, + .imageView = cyl_1_diffuse_texture_view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + }; + VkDescriptorImageInfo image_info_for_descriptor_2_in_set_0 = { + .sampler = nearest_sampler, + .imageView = cyl_1_normal_texture_view, .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, }; VkDescriptorImageInfo image_info_for_descriptor_0_in_set_1 = { - .sampler = my_texture_sampler, + .sampler = nearest_sampler, .imageView = IT1_view, .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, }; @@ -855,6 +886,15 @@ int main() { .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo = &image_info_for_descriptor_1_in_set_0, }, + { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptor_set_for_pipeline_0, + .dstBinding = 2, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &image_info_for_descriptor_2_in_set_0, + }, { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, @@ -887,7 +927,6 @@ int main() { break; for (size_t i = 0; i < events.len; i++) { Xlib_Event* ev = VecXlib_Event_at(&events, i); - // printf("%lu %lu\n", ev->xany.window, wep.win); if (ev->xany.window != wep.win) continue; Margaret_WEP_update_with_new_event(&wep, ev); @@ -1086,16 +1125,20 @@ int main() { ModelTopology_drop(cylinder_2); // destroying vulkan objects vkDestroyDescriptorPool(device, descriptor_pool, NULL); - vkDestroySampler(device, my_texture_sampler, NULL); - vkDestroyImageView(device, wood_texture_view, NULL); + vkDestroySampler(device, linear_sampler, NULL); + vkDestroySampler(device, nearest_sampler, NULL); + vkDestroyImageView(device, cyl_1_diffuse_texture_view, NULL); + vkDestroyImageView(device, cyl_1_normal_texture_view, NULL); vkDestroyBuffer(device, device_vbo_1_buffer.buffer, NULL); vkDestroyBuffer(device, device_ebo_1_buffer.buffer, NULL); vkDestroyBuffer(device, device_vbo_2_buffer.buffer, NULL); vkDestroyBuffer(device, device_ebo_2_buffer.buffer, NULL); vkDestroyBuffer(device, device_ubo_my_buffer.buffer, NULL); - vkDestroyImage(device, device_wood_texture.image, NULL); + vkDestroyImage(device, device_cyl_1_diffuse_texture.image, NULL); + vkDestroyImage(device, device_cyl_1_normal_texture.image, NULL); vkDestroyImage(device, device_IT1_image.image, NULL); + vkDestroyImage(device, device_zbuffer_image.image, NULL); vkDestroyBuffer(device, host_mem_buffer.buffer, NULL); vkFreeMemory(device, device_mem, NULL); diff --git a/src/l2/tests/r0_assets.h b/src/l2/tests/r0_assets.h index 6487494..14995c3 100644 --- a/src/l2/tests/r0_assets.h +++ b/src/l2/tests/r0_assets.h @@ -7,6 +7,7 @@ #include "../../l1/system/fileio.h" #include #include "../../../gen/pixel_masses.h" +#include "../marie/rasterization.h" typedef struct { vec3 pos; @@ -76,8 +77,8 @@ VecT_trivmove_struct_Definition(Pipeline0PointLight) VecT_trivmove_method_Definition(Pipeline0PointLight) VecT_primitive_zeroinit_method_Definition(Pipeline0PointLight) -const size_t pipeline_0_ubo_point_light_max_count = 20; -const size_t pipeline_0_ubo_spotlight_max_count = 120; +#define pipeline_0_ubo_point_light_max_count 20 +#define pipeline_0_ubo_spotlight_max_count 120 typedef struct { int spotlight_count; @@ -165,20 +166,20 @@ void TextureDataR8_draw_vertical_inner_line_maxing(TextureDataR8* self, } void TextureDataR8_draw_inner_line_maxing(TextureDataR8* self, - float x1, float y1, float x2, float y2, float r_cut, float r_decay) { - float dx = fabsf(x2 - x1); - float dy = fabsf(y2 - y1); + vec2 v1, vec2 v2, float r_cut, float r_decay) { + float dx = fabsf(v2.x - v1.x); + float dy = fabsf(v2.y - v1.y); if (dx > dy) { - if (x1 < x2) { - TextureDataR8_draw_horizontal_inner_line_maxing(self, x1, y1, x2, y2, r_cut, r_decay); + if (v1.x < v2.x) { + TextureDataR8_draw_horizontal_inner_line_maxing(self, v1.x, v1.y, v2.x, v2.y, r_cut, r_decay); } else { - TextureDataR8_draw_horizontal_inner_line_maxing(self, x2, y2, x1, y1, r_cut, r_decay); + TextureDataR8_draw_horizontal_inner_line_maxing(self, v2.x, v2.y, v1.x, v1.y, r_cut, r_decay); } } else { - if (y1 < y2) { - TextureDataR8_draw_vertical_inner_line_maxing(self, x1, y1, x2, y2, r_cut, r_decay); + if (v1.y < v2.y) { + TextureDataR8_draw_vertical_inner_line_maxing(self, v1.x, v1.y, v2.x, v2.y, r_cut, r_decay); } else { - TextureDataR8_draw_vertical_inner_line_maxing(self, x2, y2, x1, y1, r_cut, r_decay); + TextureDataR8_draw_vertical_inner_line_maxing(self, v2.x, v2.y, v1.x, v1.y, r_cut, r_decay); } } } @@ -201,35 +202,35 @@ void TextureDataR8_draw_one_segment_maxing(TextureDataR8* self, vec2 v1, vec2 v2, float r_cut, float r_decay) { TextureDataR8_draw_spot_maxing(self, v1, r_cut, r_decay); TextureDataR8_draw_spot_maxing(self, v2, r_cut, r_decay); - TextureDataR8_draw_inner_line_maxing(self, v1.x, v1.y, v2.x, v2.y, r_cut, r_decay); + TextureDataR8_draw_inner_line_maxing(self, v1, v2, r_cut, r_decay); } -TextureDataR8G8B8A8 generate_wood_texture() { - const U32 width = 100; - const U32 height = 100; - TextureDataR8G8B8A8 res = TextureDataR8G8B8A8_new(width, height); - for (U32 y = 0; y < width; y++) { - for (U32 col = 0; col < height; col++) { - *TextureDataR8G8B8A8_at(&res, col, y) = (cvec4){150, 30, 50, 255}; - } - } - for (U32 i = 0; i < 10; i++) { - for (U32 y = 0; y < height; y++) { - U32 col = 3 + i * 10 + ((30 < y + 3 * i && y - i < 60) ? 1 : 0); - - *TextureDataR8G8B8A8_at(&res, col, y) = (cvec4){130, 25, 40, 255}; - *TextureDataR8G8B8A8_at(&res, col + 1, y) = (cvec4){80, 10, 15, 255}; - *TextureDataR8G8B8A8_at(&res, col + 2, y) = (cvec4){70, 11, 12, 255}; - *TextureDataR8G8B8A8_at(&res, col + 3, y) = (cvec4){125, 20, 20, 255}; - } - } - for (U32 y = 0; y < 10; y++) { - for (U32 col = 0; col < 10; col++) { - *TextureDataR8G8B8A8_at(&res, col + 4, y + 13) = (cvec4){60, 8, 6, 255}; - } - } - return res; -} +// TextureDataR8G8B8A8 generate_wood_texture() { +// const U32 width = 100; +// const U32 height = 100; +// TextureDataR8G8B8A8 res = TextureDataR8G8B8A8_new(width, height); +// for (U32 y = 0; y < width; y++) { +// for (U32 col = 0; col < height; col++) { +// *TextureDataR8G8B8A8_at(&res, col, y) = (cvec4){150, 30, 50, 255}; +// } +// } +// for (U32 i = 0; i < 10; i++) { +// for (U32 y = 0; y < height; y++) { +// U32 col = 3 + i * 10 + ((30 < y + 3 * i && y - i < 60) ? 1 : 0); +// +// *TextureDataR8G8B8A8_at(&res, col, y) = (cvec4){130, 25, 40, 255}; +// *TextureDataR8G8B8A8_at(&res, col + 1, y) = (cvec4){80, 10, 15, 255}; +// *TextureDataR8G8B8A8_at(&res, col + 2, y) = (cvec4){70, 11, 12, 255}; +// *TextureDataR8G8B8A8_at(&res, col + 3, y) = (cvec4){125, 20, 20, 255}; +// } +// } +// for (U32 y = 0; y < 10; y++) { +// for (U32 col = 0; col < 10; col++) { +// *TextureDataR8G8B8A8_at(&res, col + 4, y + 13) = (cvec4){60, 8, 6, 255}; +// } +// } +// return res; +// } ModelTopology generate_one_fourth_of_a_cylinder(float w, float r, U32 k) { assert(k >= 1); @@ -244,49 +245,73 @@ ModelTopology generate_one_fourth_of_a_cylinder(float w, float r, U32 k) { VecVertex_append(&vertices, (Vertex){.pos = {w, 0, 0}, .tex = v1tex}); VecVertex_append(&vertices, (Vertex){.pos = {0, r, 0}, .tex = v2tex}); VecVertex_append(&vertices, (Vertex){.pos = {w, r, 0}, .tex = v3tex}); - VecVertex_append(&vertices, (Vertex){.pos = {0, 0, r}, .tex = {r / (2 * r + w), 0}}); - VecVertex_append(&vertices, (Vertex){.pos = {w, 0, r}, .tex = {(r + w) / (2 * r + w), 0}}); + VecVertex_append(&vertices, (Vertex){.pos = {0, 0, -r}, .tex = {r / (2 * r + w), 0}}); + VecVertex_append(&vertices, (Vertex){.pos = {w, 0, -r}, .tex = {(r + w) / (2 * r + w), 0}}); for (U32 i = 1; i <= k; i++) { VecVertex_append(&vertices, (Vertex){ - .pos = {0, cosf(a * i) * r, sinf(a * i) * r}, + .pos = {0, cosf(a * i) * r, -sinf(a * i) * r}, .tex = vec2_add_vec2(v0tex, (vec2){r / (2 * r + w) * -sinf(a * i), r / (2 * r + k * l) * cos(a * i)}) }); } for (U32 i = 1; i <= k; i++) { VecVertex_append(&vertices, (Vertex){ - .pos = {w, cosf(a * i) * r, sinf(a * i) * r}, + .pos = {w, cosf(a * i) * r, -sinf(a * i) * r}, .tex = vec2_add_vec2(v1tex, (vec2){r / (2 * r + w) * sinf(a * i), r / (2*r + k * l) * cos(a * i)}) }); } for (U32 i = 1; i <= k; i++) { VecVertex_append(&vertices, (Vertex){ - .pos = {0, cosf(a * i) * r, sinf(a * i) * r}, + .pos = {0, cosf(a * i) * r, -sinf(a * i) * r}, .tex = {v2tex.x, v2tex.y + i * l / (2*r + k * l)} }); } for (U32 i = 1; i <= k; i++) { VecVertex_append(&vertices, (Vertex){ - .pos = {w, cosf(a * i) * r, sinf(a * i) * r}, + .pos = {w, cosf(a * i) * r, -sinf(a * i) * r}, .tex = {v3tex.x, v3tex.y + i * l / (2*r + k * l)} }); } VecU32 indexes = VecU32_new(); // todo: reserve 3 * (2+2+2*k+2*k)< { - U32 _span_0[] = { 5, 0, 1, 5, 4, 0, 1, 0, 3, 3, 0, 2 }; + U32 _span_0[] = {5, 1, 0, 5, 0, 4, 1, 3, 0, 3, 2, 0}; VecU32_append_span(&indexes, (ConstSpanU32){.data = _span_0, .len = ARRAY_SIZE(_span_0)}); } for (U32 i = 1; i <= k; i++) { U32 _span_1[] = { - 0, 5 + i, i > 1 ? 5 + i - 1 : 2, - 1, i > 1 ? 5 + k + i - 1 : 3, 5 + k + i, - i > 1 ? 5 + 2 * k + i - 1 : 2, 5 + 2 * k + i, i > 1 ? 5 + 3 * k + i - 1 : 3, - 5 + 3 * k + i, i > 1 ? 5 + 3 * k + i - 1 : 3, 5 + 2 * k + i + 0, i > 1 ? 5 + i - 1 : 2, 5 + i, + 1, 5 + k + i, i > 1 ? 5 + k + i - 1 : 3, + i > 1 ? 5 + 2 * k + i - 1 : 2, i > 1 ? 5 + 3 * k + i - 1 : 3, 5 + 2 * k + i, + 5 + 3 * k + i, 5 + 2 * k + i, i > 1 ? 5 + 3 * k + i - 1 : 3, }; VecU32_append_span(&indexes, (ConstSpanU32){.data = _span_1, .len = ARRAY_SIZE(_span_1)}); } return (ModelTopology){.vertices = vertices, .indexes = indexes}; } +#define vec2_drop(x) {} +#define vec2_clone(x) (*(x)) + +VecT_trivmove_struct_Definition(vec2) +VecT_trivmove_method_Definition(vec2) +VecT_primitive_zeroinit_method_Definition(vec2) +SpanT_struct_Definition(vec2) +SpanT_method_Definition(vec2) +SpanT_VecT_method_Definition(vec2) + +void TextureDataR8_draw_perimeter_maxing(TextureDataR8* self, ConstSpanvec2 P) { + float r_cut = 2; + float r_decay = 1; + size_t S = P.len; + for (size_t i = 0; i < S; i++) { + TextureDataR8_draw_spot_maxing(self, *ConstSpanvec2_at(P, i), r_cut, r_decay); + } + for (size_t i = 1; i < S; i++) { + TextureDataR8_draw_inner_line_maxing(self, *ConstSpanvec2_at(P, i - 1), *ConstSpanvec2_at(P, i), r_cut, r_decay); + } + if (S > 2) + TextureDataR8_draw_inner_line_maxing(self, *ConstSpanvec2_at(P, S - 1), *ConstSpanvec2_at(P, 0), r_cut, r_decay); +} + typedef struct { vec2 bl; vec2 tr; @@ -303,7 +328,7 @@ VecT_primitive_zeroinit_method_Definition(Wimbzle) typedef struct { vec2 center; - vec2 rad; + float rad; float hc; } Nibzle; @@ -315,22 +340,22 @@ VecT_trivmove_method_Definition(Nibzle) VecT_primitive_zeroinit_method_Definition(Nibzle) typedef struct { - VecWimbzle wibmzles; + VecWimbzle wimbzles; VecNibzle nibzles; } Bublazhuzhka; Bublazhuzhka Bublazhuzhka_new() { - return (Bublazhuzhka){.wibmzles = VecWimbzle_new(), .nibzles = VecNibzle_new()}; + return (Bublazhuzhka){.wimbzles = VecWimbzle_new(), .nibzles = VecNibzle_new()}; } void Bublazhuzhka_drop(Bublazhuzhka self) { - VecWimbzle_drop(self.wibmzles); + VecWimbzle_drop(self.wimbzles); VecNibzle_drop(self.nibzles); } -void Bublazhuzhka_TextureDataR8_draw_maxing(const Bublazhuzhka* self, TextureDataR8* canvas, vec2) { - for (size_t i = 0; i < self->wibmzles.len; i++) { - Wimbzle rect = *VecWimbzle_cat(&self->wibmzles, i); +void Bublazhuzhka_TextureDataR8_draw_maxing(const Bublazhuzhka* self, TextureDataR8* canvas, mat3x2 trop) { + for (size_t i = 0; i < self->wimbzles.len; i++) { + Wimbzle rect = *VecWimbzle_cat(&self->wimbzles, i); vec2 B = {rect.tr.x + rect.brd, rect.tr.y + rect.brd}; vec2 C = {rect.bl.x - rect.brd, rect.bl.y - rect.brd}; vec2 A = {C.x, B.y}; @@ -340,16 +365,199 @@ void Bublazhuzhka_TextureDataR8_draw_maxing(const Bublazhuzhka* self, TextureDat vec2 E = {G.x, F.y}; vec2 H = {F.x, G.y}; - TextureDataR8_draw_one_segment_maxing(canvas, A, B, 1.5, 1); + vec2 p1[4] = {mat3x2_mul_vec3(trop, vec2_and_one(A)), mat3x2_mul_vec3(trop, vec2_and_one(B)), + mat3x2_mul_vec3(trop, vec2_and_one(D)), mat3x2_mul_vec3(trop, vec2_and_one(C))}; + TextureDataR8_draw_perimeter_maxing(canvas, (ConstSpanvec2){.data = p1, ARRAY_SIZE(p1)}); + vec2 p2[4] = {mat3x2_mul_vec3(trop, vec2_and_one(E)), mat3x2_mul_vec3(trop, vec2_and_one(F)), + mat3x2_mul_vec3(trop, vec2_and_one(H)), mat3x2_mul_vec3(trop, vec2_and_one(G))}; + TextureDataR8_draw_perimeter_maxing(canvas, (ConstSpanvec2){.data = p2, ARRAY_SIZE(p2)}); + } + + for (size_t i = 0; i < self->nibzles.len; i++) { + Nibzle sphere = *VecNibzle_cat(&self->nibzles, i); + Vecvec2 p = Vecvec2_new_zeroinit(13); + for (int j = 0; j < 13; j++) { + float a = (float)j * 2 * M_PI / 13; + *Vecvec2_at(&p, j) = vec2_add_vec2(sphere.center, vec2_mul_scal(marie_trigonom_circle(a), sphere.rad)); + } + TextureDataR8_draw_perimeter_maxing(canvas, Vecvec2_to_ConstSpanvec2(&p)); + Vecvec2_drop(p); + TextureDataR8_draw_spot_maxing(canvas, mat3x2_mul_vec3(trop, vec2_and_one(sphere.center)), 3, 1); } } -#define vec2_drop(x) {} -#define vec2_clone(x) (*(x)) +Bublazhuzhka fill_rectangle_with_crap(float w, float h) { + // Bublazhuzhka res = Bublazhuzhka_new(); + S32 k = MAX_S32(0, (S32)floorf((w/h + 0.1f) / 0.7f)); + VecWimbzle wimbzles = VecWimbzle_new_zeroinit(k); + VecNibzle nibzles = VecNibzle_new_zeroinit(2 * k); + float start = k != 1 ? h * 0.2f : ((w - 0.2f * h) / 2); + float d = k > 1 ? ((w - h * 0.4f - h * 0.2f * k) / (float)(k - 1)) : 0; + for (S32 i = 0; i < k; i++) { + float x = start + (d + h * 0.2) * i; + *VecWimbzle_at(&wimbzles, i) = (Wimbzle){.bl = {x + 0.02 * h, 0.27 * h}, .tr = {x + 0.18 * h, h * 0.73}, .height = h * 0.03, .brd = h * 0.03}; + /* hc is a height coefficient*/ + *VecNibzle_at(&nibzles, 2 * i) = (Nibzle){.center = {x + 0.10 * h, 0.11 * h}, .rad = h * 0.05, .hc = 0.75}; + *VecNibzle_at(&nibzles, 2 * i + 1) = (Nibzle){.center = {x + 0.10 * h, 0.89 * h}, .rad = h * 0.05, .hc = 0.75}; + } + return (Bublazhuzhka){.wimbzles = wimbzles, .nibzles = nibzles}; +} + +vec2 Bublazhuzhka_get_derivative(const Bublazhuzhka* self, vec2 v) { + vec2 sum = { 0 }; + for (size_t i = 0; i < self->wimbzles.len; i++) { + Wimbzle rect = *VecWimbzle_cat(&self->wimbzles, i); + vec2 B = {rect.tr.x + rect.brd, rect.tr.y + rect.brd}; + vec2 C = {rect.bl.x - rect.brd, rect.bl.y - rect.brd}; + vec2 A = {C.x, B.y}; + vec2 D = {B.x, C.y}; + vec2 F = rect.tr; + vec2 G = rect.bl; + vec2 E = {G.x, F.y}; + vec2 H = {F.x, G.y}; + float slp = rect.height / rect.brd; + if (A.x < v.x && v.x < E.x && marie_surface(E, A, v) > 0 && marie_surface(C, G, v) > 0) { + sum.x += slp; + } else if (F.x < v.x && v.x < B.x && marie_surface(B, F, v) > 0 && marie_surface(H, D, v) > 0) { + sum.x -= slp; + } else if (C.y < v.y && v.y < G.y && marie_surface(G, C, v) > 0 && marie_surface(D, H, v) > 0) { + sum.y += slp; + } else if (F.y < v.y && v.y < B.y && marie_surface(A, E, v) > 0 && marie_surface(F, B, v) > 0) { + sum.y -= slp; + } + } + for (size_t i = 0; i < self->nibzles.len; i++) { + Nibzle sphere = *VecNibzle_cat(&self->nibzles, i); + float sq_h = pow2f(sphere.rad) - pow2f(v.x - sphere.center.x) - pow2f(v.y - sphere.center.y); + if (sq_h <= 0) + continue; + float w = sphere.hc / sqrtf(sq_h); + sum = vec2_add_vec2(sum, vec2_mul_scal(vec2_minus_vec2(sphere.center, v), w)); + } + return sum; +} + +cvec3 compress_normal_vec_into_norm_texel(vec3 n) { + return (cvec3){(U32)roundf(255 * (n.x + 1) / 2), (U32)roundf(255 * (n.y + 1) / 2), (U32)roundf(255 * (n.z + 1) / 2)}; +} + + +typedef struct { + /* (guest, param) -> normal vector in model space */ + vec3 (*fn)(void*, vec2); + void* guest; +} FnNormalVectorGenCallback; + +typedef struct { + TextureDataR8G8B8* tex; + FnNormalVectorGenCallback my_client; +} draw_polygon_on_normal_texture_smooth_param_surf_H_DrawGuest; + +void draw_polygon_on_normal_texture_smooth_param_surf_h_draw_cb(void* ug, S32 x, S32 y, vec4 attr) { + draw_polygon_on_normal_texture_smooth_param_surf_H_DrawGuest* g = ug; + vec3 normal = g->my_client.fn(g->my_client.guest, (vec2){attr.x, attr.y}); + *TextureDataR8G8B8_at(g->tex, x, y) = compress_normal_vec_into_norm_texel(normal); +} + +void draw_polygon_on_normal_texture_smooth_param_surf( + TextureDataR8G8B8* tex, vec2 pa, vec2 pb, vec2 pc, mat3x2 trop, FnNormalVectorGenCallback cb + ) { + draw_polygon_on_normal_texture_smooth_param_surf_H_DrawGuest aboba = {.tex = tex, .my_client = cb}; + marie_rasterize_triangle_with_attr( + (MariePlaneVertAttr){.pos = mat3x2_mul_vec3(trop, vec2_and_one(pa)), .attr = {pa.x, pa.y, 0, 0} }, + (MariePlaneVertAttr){.pos = mat3x2_mul_vec3(trop, vec2_and_one(pb)), .attr = {pb.x, pb.y, 0, 0} }, + (MariePlaneVertAttr){.pos = mat3x2_mul_vec3(trop, vec2_and_one(pc)), .attr = {pc.x, pc.y, 0, 0} }, + (FnMarieRasterizerCallback){draw_polygon_on_normal_texture_smooth_param_surf_h_draw_cb, (void*)&aboba}); +} + + +typedef struct { + /* (guest, param) -> normal vector in model space */ + vec3 (*fn)(void*, vec3); + void* guest; +} FnNormalVectorGenExaggParamCallback; + +typedef struct { + TextureDataR8G8B8* tex; + FnNormalVectorGenExaggParamCallback my_client; +} draw_polygon_on_normal_texture_exaggerated_param_surf_H_DrawGuest; + +void draw_polygon_on_normal_texture_exaggerated_param_surf_draw_cb(void* ug, S32 x, S32 y, vec4 attr) { + draw_polygon_on_normal_texture_exaggerated_param_surf_H_DrawGuest* g = ug; + vec3 normal = g->my_client.fn(g->my_client.guest, (vec3){attr.x, attr.y, attr.z}); + *TextureDataR8G8B8_at(g->tex, x, y) = compress_normal_vec_into_norm_texel(normal); +} + +/* We can't derive texture coordinates from parameter space coordinates, you have to do it yourself */ +void draw_polygon_on_normal_texture_nat_cords_exaggerated_param_surf( + TextureDataR8G8B8* tex, vec2 ta, vec2 tb, vec2 tc, vec3 pa, vec3 pb, vec3 pc, FnNormalVectorGenExaggParamCallback cb + ) { + draw_polygon_on_normal_texture_exaggerated_param_surf_H_DrawGuest aboba = {.tex = tex, .my_client = cb}; + marie_rasterize_triangle_with_attr( + (MariePlaneVertAttr){.pos = ta, .attr = {pa.x, pa.y, pa.z, 0} }, + (MariePlaneVertAttr){.pos = tb, .attr = {pb.x, pb.y, pb.z, 0} }, + (MariePlaneVertAttr){.pos = tc, .attr = {pc.x, pc.y, pc.z, 0} }, + (FnMarieRasterizerCallback){draw_polygon_on_normal_texture_exaggerated_param_surf_draw_cb, (void*)&aboba}); +} +// todo: add a version for that function with non-native coordinate system (on vertex) (like I did with absolutely flat surface) + + +typedef struct { + TextureDataR8G8B8* tex; + cvec3 normal_compr; +} draw_polygon_on_normal_texture_absolutely_flat_H_DrawGuest; + +void draw_polygon_on_normal_texture_absolutely_flat_h_draw_cb(void* ug, S32 x, S32 y, vec4) { + draw_polygon_on_normal_texture_absolutely_flat_H_DrawGuest* g = ug; + *TextureDataR8G8B8_at(g->tex, x, y) = g->normal_compr; +} + +void draw_polygon_on_normal_texture_nat_cords_absolutely_flat(TextureDataR8G8B8* tex, + vec2 ta, vec2 tb, vec2 tc, vec3 c_normal + ) { + draw_polygon_on_normal_texture_absolutely_flat_H_DrawGuest aboba = {tex, compress_normal_vec_into_norm_texel(c_normal)}; + marie_rasterize_triangle_with_attr((MariePlaneVertAttr){.pos = ta}, (MariePlaneVertAttr){.pos = tb}, + (MariePlaneVertAttr){.pos = tc}, (FnMarieRasterizerCallback){ + .fn = draw_polygon_on_normal_texture_absolutely_flat_h_draw_cb, .guest = (void*)&aboba}); +} + +void draw_polygon_on_normal_texture_absolutely_flat(TextureDataR8G8B8* tex, + vec2 pa, vec2 pb, vec2 pc, mat3x2 trop, vec3 c_normal + ) { + draw_polygon_on_normal_texture_nat_cords_absolutely_flat(tex, mat3x2_mul_vec3(trop, vec2_and_one(pa)), + mat3x2_mul_vec3(trop, vec2_and_one(pb)), mat3x2_mul_vec3(trop, vec2_and_one(pc)), c_normal); +} + + +typedef struct { + /* (guest, param) -> height gradient */ + vec2 (*fn)(void*, vec2); + void* guest; +} FnHeightMapGradFlatSurfCallback; + +typedef struct { + mat3 surf_orient; + FnHeightMapGradFlatSurfCallback my_client; +} draw_polygon_on_normal_texture_flat_param_surf_H_DrawGuest; + +vec3 draw_polygon_on_normal_texture_flat_param_surf_h_draw_cb(void* ug, vec2 p) { + draw_polygon_on_normal_texture_flat_param_surf_H_DrawGuest* g = ug; + vec2 grad = g->my_client.fn(g->my_client.guest, p); + return mat3_mul_vec3(g->surf_orient, marie_normal_from_tang_space_gradient(grad.x, grad.y)); +} + +/* The simplest case of normal texture generation: for a smooth flat surface of a polygon */ +void draw_polygon_on_normal_texture_flat_param_surf(TextureDataR8G8B8* tex, vec2 pa, vec2 pb, vec2 pc, mat3x2 trop, + mat3 surf_orient, FnHeightMapGradFlatSurfCallback height_map_cb + ) { + draw_polygon_on_normal_texture_flat_param_surf_H_DrawGuest aboba = {surf_orient, height_map_cb}; + draw_polygon_on_normal_texture_smooth_param_surf(tex, pa, pb, pc, trop, (FnNormalVectorGenCallback){ + .fn = draw_polygon_on_normal_texture_flat_param_surf_h_draw_cb, .guest = (void*)&aboba}); +} + + + -VecT_trivmove_struct_Definition(vec2) -VecT_trivmove_method_Definition(vec2) -VecT_primitive_zeroinit_method_Definition(vec2) TextureDataR8 generate_tex_template_for_one_fourth_of_a_cylinder(float s_resol, float w, float r, U32 k) { assert(k >= 1); @@ -382,22 +590,24 @@ TextureDataR8 generate_tex_template_for_one_fourth_of_a_cylinder(float s_resol, for (size_t i = 1; i <= k; i++) { Vecvec2_append(&P, (vec2){r - r * sinf(a * i), r + r * cos(a * i)}); } - size_t S = 6 + 4 * k; - assert(P.len == S); - float r_cut = 2; - float r_decay = 1; - for (size_t i = 0; i < S; i++) { - vec2 p = vec2_mul_vec2(*Vecvec2_at(&P, i), cord_resol); - TextureDataR8_draw_spot_maxing(&res, p, r_cut, r_decay); - } - for (size_t i = 0; i < S; i++) { - vec2 pp = vec2_mul_vec2(*Vecvec2_at(&P, i ? i - 1 : S - 1), cord_resol); - vec2 p = vec2_mul_vec2(*Vecvec2_at(&P, i), cord_resol); - TextureDataR8_draw_inner_line_maxing(&res, pp.x, pp.y, p.x, p.y, r_cut, r_decay); + for (size_t i = 0; i < P.len; i++) { + *Vecvec2_at(&P, i) = vec2_mul_vec2(*Vecvec2_at(&P, i), cord_resol); } + TextureDataR8_draw_perimeter_maxing(&res, Vecvec2_to_ConstSpanvec2(&P)); + + Bublazhuzhka crap_on_back_side = fill_rectangle_with_crap(w, r); + Bublazhuzhka_TextureDataR8_draw_maxing(&crap_on_back_side, &res, + (mat3x2){.x.x = cord_resol.x, .y.y = cord_resol.y, .z = vec2_mul_vec2((vec2){r, r}, cord_resol)}); return res; } +/* Use it as a callback in normal map drawing functions that work with smooth (smooth / flat / cylindrical) + * height maps. Guest pointer is of type Bublazhuzhka* */ +vec2 height_map_cb_that_uses_bublazhuzhka(void* ug, vec2 v) { + Bublazhuzhka* bzh = ug; + return Bublazhuzhka_get_derivative(bzh, v); +} + TextureDataR8G8B8 generate_normal_tex_for_one_fourth_of_a_cylinder(float s_resol, float w, float r, U32 k) { assert(k >= 1); const float a = M_PI_2f / (float)k; @@ -409,35 +619,40 @@ TextureDataR8G8B8 generate_normal_tex_for_one_fourth_of_a_cylinder(float s_resol const vec2 v1tex = {r + w, r}; const vec2 v2tex = {r, 2 * r}; const vec2 v3tex = {r + w, 2 * r}; + const vec2 v4tex = {r, 0}; + const vec2 v5tex = {r + w, 0}; TextureDataR8G8B8 res = TextureDataR8G8B8_new(width_pix, height_pix); - // Vecvec2 P = Vecvec2_new(); // todo: reserve 6 + k * 4 - // for (size_t i = k; i > 0; i--) { - // Vecvec2_append(&P, (vec2){r + w + r * sinf(a * i), r + r * cos(a * i)}); - // } - // Vecvec2_append(&P, v3tex); - // for (size_t i = 1; i <= k; i++) { - // Vecvec2_append(&P, (vec2){r + w, 2 * r + i * l}); - // } - // for (size_t i = k; i > 0; i--) { - // Vecvec2_append(&P, (vec2){r, 2 * r + i * l}); - // } - // Vecvec2_append(&P, v2tex); - // for (size_t i = 1; i <= k; i++) { - // Vecvec2_append(&P, (vec2){r - r * sinf(a * i), r + r * cos(a * i)}); - // } - // size_t S = 6 + 4 * k; - // assert(P.len == S); - // float r_cut = 2; - // float r_decay = 1; - // for (size_t i = 0; i < S; i++) { - // vec2 p = vec2_mul_vec2(*Vecvec2_at(&P, i), cord_resol); - // TextureDataR8_draw_spot_maxing(&res, p.x, p.y, r_cut, r_decay); - // } - // for (size_t i = 0; i < S; i++) { - // vec2 pp = vec2_mul_vec2(*Vecvec2_at(&P, i ? i - 1 : S - 1), cord_resol); - // vec2 p = vec2_mul_vec2(*Vecvec2_at(&P, i), cord_resol); - // TextureDataR8_draw_inner_line_maxing(&res, pp.x, pp.y, p.x, p.y, r_cut, r_decay); - // } + + Bublazhuzhka crap_on_the_back_side = fill_rectangle_with_crap(w, r); + mat3x2 trop_back_side = {.x.x = cord_resol.x, .y.y = cord_resol.y, .z = vec2_mul_vec2((vec2){r, r}, cord_resol)}; + mat3 orient_back_side = {.x = {1, 0, 0}, .y = {0, 0, 1}, {0, 1, 0}}; + draw_polygon_on_normal_texture_flat_param_surf(&res, (vec2){0, 0}, (vec2){w, 0}, (vec2){w, r}, trop_back_side, orient_back_side, + (FnHeightMapGradFlatSurfCallback){.fn = height_map_cb_that_uses_bublazhuzhka, .guest = &crap_on_the_back_side}); + draw_polygon_on_normal_texture_flat_param_surf(&res, (vec2){0, 0}, (vec2){0, r}, (vec2){w, r}, trop_back_side, orient_back_side, + (FnHeightMapGradFlatSurfCallback){.fn = height_map_cb_that_uses_bublazhuzhka, .guest = &crap_on_the_back_side}); + + mat3x2 str = {.x.x = cord_resol.x, .y.y = cord_resol.y}; + draw_polygon_on_normal_texture_absolutely_flat(&res, v0tex, v1tex, v4tex, str, (vec3){0, -1, 0}); + draw_polygon_on_normal_texture_absolutely_flat(&res, v1tex, v4tex, v5tex, str, (vec3){0, -1, 0}); + for (size_t i = 0; i < k; i++) { + vec2 A = {r - sinf(i * a) * r, r + cosf(i * a) * r}; + vec2 B = {r - sinf((i + 1) * a) * r, r + cosf((i + 1) * a) * r}; + draw_polygon_on_normal_texture_absolutely_flat(&res, A, B, (vec2){r, r}, str, (vec3){-1, 0, 0}); + } + for (size_t i = 0; i < k; i++) { + vec2 A = {r + w + sinf(i * a) * r, r + cos(i * a) * r}; + vec2 B = {r + w + sinf((i + 1) * a) * r, r + cos((i + 1) * a) * r}; + draw_polygon_on_normal_texture_absolutely_flat(&res, A, B, (vec2){r + w, r}, str, (vec3){1, 0, 0}); + } + for (size_t i = 0; i < k; i++) { + vec2 A = {r, 2 * r + i * l}; + vec2 B = {r + w, 2 * r + i * l}; + vec2 C = {r, 2 * r + i * l + l}; + vec2 D = {r + w, 2 * r + i * l + l}; + vec3 n = {0, cos(a / 2 + a * i), -sin(a / 2 + a * i)}; + draw_polygon_on_normal_texture_absolutely_flat(&res, A, B, C, str, n); + draw_polygon_on_normal_texture_absolutely_flat(&res, D, B, C, str, n); + } return res; } diff --git a/src/l2/tests/r0_tex_init_prep.c b/src/l2/tests/r0_tex_init_prep.c index 5edfbf4..ea06da8 100644 --- a/src/l2/tests/r0_tex_init_prep.c +++ b/src/l2/tests/r0_tex_init_prep.c @@ -16,19 +16,29 @@ void draw_cool_triangle(TextureDataR8G8B8* tex, vec2 A, vec2 B, vec2 C) { } int main() { - // TextureDataR8 tex_1 = generate_tex_template_for_one_fourth_of_a_cylinder(20, 10, 2, 6); - // TextureDataR8_write_to_file(&tex_1, "log_10_2_6_TEMPLATE.r8"); - // TextureDataR8_drop(tex_1); - TextureDataR8G8B8 t = TextureDataR8G8B8_new(1000, 1000);\ - vec2 center = {500, 500}; - for (int i = 0; i < 70; i++) { - float a1 = (float)i * 2 * M_PIf / 70; - float a2 = (float)(i + 1) * 2 * M_PIf / 70; - vec2 A = vec2_add_vec2(center, vec2_mul_scal((vec2){cosf(a1), sinf(a1)}, 480)); - vec2 B = vec2_add_vec2(center, vec2_mul_scal((vec2){cosf(a2), sinf(a2)}, 480)); - draw_cool_triangle(&t, B, A, center); + TextureDataR8 tex_1 = generate_tex_template_for_one_fourth_of_a_cylinder(120, 10, 2, 6); + TextureDataR8_write_to_file(&tex_1, "log_10_2_6_TEMPLATE.a8"); + TextureDataR8_drop(tex_1); + TextureDataR8G8B8 tex_2 = generate_normal_tex_for_one_fourth_of_a_cylinder(120, 10, 2, 6); + TextureDataR8G8B8A8 tex_2_big = TextureDataR8G8B8A8_new(tex_2.width, TextureDataR8G8B8_get_height(&tex_2)); + for (size_t i = 0; i < tex_2.pixels.len; i++) { + cvec3 rgb = *Veccvec3_at(&tex_2.pixels, i); + *Veccvec4_at(&tex_2_big.pixels, i) = (cvec4){rgb.x, rgb.y, rgb.z, 255}; } - TextureDataR8G8B8_write_to_file(&t, "experiment.r8g8b8"); - TextureDataR8G8B8_drop(t); + TextureDataR8G8B8A8_write_to_file(&tex_2_big, "log_10_2_6_NORMAL.r8g8b8a8"); + TextureDataR8G8B8A8_drop(tex_2_big); + TextureDataR8G8B8_drop(tex_2); + + // TextureDataR8G8B8 t = TextureDataR8G8B8_new(1000, 1000);\ + // vec2 center = {500, 500}; + // for (int i = 0; i < 70; i++) { + // float a1 = (float)i * 2 * M_PIf / 70; + // float a2 = (float)(i + 1) * 2 * M_PIf / 70; + // vec2 A = vec2_add_vec2(center, vec2_mul_scal((vec2){cosf(a1), sinf(a1)}, 480)); + // vec2 B = vec2_add_vec2(center, vec2_mul_scal((vec2){cosf(a2), sinf(a2)}, 480)); + // draw_cool_triangle(&t, B, A, center); + // } + // TextureDataR8G8B8_write_to_file(&t, "experiment.r8g8b8"); + // TextureDataR8G8B8_drop(t); return 0; } diff --git a/src/l2/tests/test_shaders/glsl/0/0.frag b/src/l2/tests/test_shaders/glsl/0/0.frag index 1ce099b..3bd7fd2 100644 --- a/src/l2/tests/test_shaders/glsl/0/0.frag +++ b/src/l2/tests/test_shaders/glsl/0/0.frag @@ -6,6 +6,7 @@ layout(location = 0) out vec4 fin_color; layout(binding = 1) uniform sampler2D color_tex; +layout(binding = 2) uniform sampler2D normal_map; struct Pipeline0Spotlight { @@ -31,5 +32,5 @@ layout(std140, binding = 0) uniform Pipeline0UBO }; void main(){ - fin_color = texture(color_tex, fsin_tex); + fin_color = texture(normal_map, fsin_tex); } diff --git a/src/l2/tests/test_textures/bitmap_converter.py b/src/l2/tests/test_textures/bitmap_converter.py index 8cc0983..3c8ab66 100755 --- a/src/l2/tests/test_textures/bitmap_converter.py +++ b/src/l2/tests/test_textures/bitmap_converter.py @@ -1,9 +1,7 @@ #!/usr/bin/env python3 -""" -raw_png_conv.py -=============== -Convert between custom bottom‑up raw files (.r8g8b8a8 / .r8b8g8 / .r8) +""" +Convert between custom bottom‑up raw files (.r8g8b8a8 / .r8b8g8 / .r8 / .a8) and normal PNG using Pillow. Format @@ -14,6 +12,7 @@ pixel data rows bottom‑first: * .r8g8b8a8 : R G B A (4× uint8) * .r8b8g8 : R G B (3× uint8) * .r8 : R (1× uint8 <- grayscale) + * .a8 : A (can convert from it, but can't convert to it) CLI --- @@ -29,10 +28,12 @@ from PIL import Image # Helpers # --------------------------------------------------------------------- # + RAW_FORMATS = { ".r8g8b8a8": {"pix_size": 4, "mode": "RGBA"}, ".r8g8b8": {"pix_size": 3, "mode": "RGB"}, ".r8": {"pix_size": 1, "mode": "L"}, + ".a8": {"pix_size": 1, "mode": None}, # special-cased (alpha-only) } @@ -43,37 +44,52 @@ def get_fmt(path: Path): return fmt -def read_raw(path: Path) -> Image.Image: - """Load any supported raw file -> Pillow Image.""" - spec = get_fmt(path) +def read_header_and_data(path: Path, pix_size: int): with path.open("rb") as f: header = f.read(8) if len(header) != 8: raise ValueError("File too short for header") w, h = struct.unpack(" Image.Image: + """Load any supported raw file -> Pillow Image.""" + spec = get_fmt(path) + w, h, data = read_header_and_data(path, spec["pix_size"]) + row_len = w * spec["pix_size"] - rows = [data[i : i + row_len] for i in range(0, expected, row_len)] - img_bytes = b"".join(reversed(rows)) # flip bottom‑up -> top‑down - return Image.frombytes(spec["mode"], (w, h), img_bytes) + rows = [data[i : i + row_len] for i in range(0, len(data), row_len)] + top_down = b"".join(reversed(rows)) # flip bottom‑up -> top‑down + + # Special handling for .a8 (alpha-only -> LA with black color) + if path.suffix.lower() == ".a8": + big = bytearray(4 * len(top_down)) + big[3::4] = top_down + big = bytes(big) + return Image.frombytes("RGBA", (w, h), big) + + # Normal cases can be constructed directly + return Image.frombytes(spec["mode"], (w, h), top_down) def write_raw(img: Image.Image, path: Path) -> None: """Write Pillow Image -> raw file chosen by path suffix.""" + ext = path.suffix.lower() spec = get_fmt(path) - # Convert to required mode - if img.mode != spec["mode"]: - if spec["mode"] == "L": - img = img.convert("L") - elif spec["mode"] == "RGB": - img = img.convert("RGB") - else: # RGBA - img = img.convert("RGBA") + + if ext == ".a8": + raise ValueError("Don't convert to .a8 format") + + target_mode = spec["mode"] + if img.mode != target_mode: + img = img.convert(target_mode) w, h = img.size data = img.tobytes() diff --git a/src/l2/tests/test_textures/log_10_2_6_TEMPLATE.png b/src/l2/tests/test_textures/log_10_2_6_TEMPLATE.png deleted file mode 100644 index fed645aed7c52a1c4c4c46ac76f75fdc40292ffa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1084 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|`#FFF1Ix$R@<5W=)5S5QV$R#UhW^p%5=TD@ zvuj44a8fQ!Fbv{MJ$0jzUqDfGqmiQVl!Mkn-UZ7Syh(lG5Z*t{YD3hfBk2XPC(BnV zP1xkiDdnuz0Iib zd_bozPaETf93OM9ASw5YQxDcXeQeNln1fH*qUQmKanRuUfv-92C*5o04;u709>3f_ zx$lQvjRq%N1)Oov;C`rfX#c5dq5clqC~5(8h|SQBQyKkmzYd%xXD3pqCL{cglMU!C92?>*PPREGc{?Iw7?|OdHEPuzUl$CPnGjgWC;oW~rO}~4ts_yhBW&w=f8ZIwt z_dKb&ft?{+#ru;*+8M2rrVQ&(hF#peWU1>uHUkCk4>bp3b0#t`a9gGR*lV@-wF`>R z|5R?>ZLMJaqh@{X|JM^mA1r!dc&V5%q+zAL@XYv`hkMLK=7)!r`eCL`x98t8eRpWj!96i^eth|6p1*&a_HV(5{-b*HUGZt%ujlF{l=~j%b%+1?|9himJykAb^AiWsHfrc5%~h1n8{}t{LIbH n&p4CQA#(Kane(Ny73CRsuZ%BPC8d`ODi%Cl{an^LB{Ts5`78-* diff --git a/src/l2/tests/test_textures/log_10_2_6_v2.png b/src/l2/tests/test_textures/log_10_2_6_v2.png deleted file mode 100644 index 615b8206d3ffbf45108c60f297db3cf878022491..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18647 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