diff --git a/build.bat b/build.bat index 9ddb773..260c813 100644 --- a/build.bat +++ b/build.bat @@ -1,4 +1,9 @@ REM cd build 2>NUL && cd .. || mkdir build if not exist build\ mkdir build || goto :EOF -clang++ src/main.cpp -o build/window.exe -O0 -g -gcodeview -lgdi32 -lvulkan-1 -I%VULKAN_SDK%/Include -L%VULKAN_SDK%/Lib -Wl,-pdb= +REM clang++ src/main.cpp -o build/window.exe -O0 -g -gcodeview -stdlib=libc++ -lunwind -lgdi32 -lkernel32 -lvulkan-1 -lSPIRV -lSPIRV-Tools -lSPIRV-Tools-diff -lSPIRV-Tools-opt -lSPVRemapper -lglslang -lOSDependent -lGenericCodeGen -lMachineIndependent -lglslang-default-resource-limits -I%VULKAN_SDK%/Include -L%VULKAN_SDK%/Lib -Wl,-pdb= -v + +set VSCMD_SKIP_SENDTELEMETRY=1 +set VCPKG_KEEP_ENV_VARS=VSCMD_SKIP_SENDTELEMETRY +vcvarsall.bat x64 && cl.exe /Fe:build\window.exe /std:c++20 /Od /MDd /EHsc -I%VULKAN_SDK%/Include src/main.cpp user32.lib gdi32.lib kernel32.lib vulkan-1.lib SPIRV.lib SPIRV-Toolsd.lib SPIRV-Tools-diffd.lib SPIRV-Tools-optd.lib SPVRemapperd.lib glslangd.lib OSDependentd.lib GenericCodeGend.lib MachineIndependentd.lib glslang-default-resource-limitsd.lib /link /DEBUG:FULL /IGNORE:4099 /LIBPATH:%VULKAN_SDK%/Lib + diff --git a/src/main.cpp b/src/main.cpp index e1c8527..eb72ca9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,19 +7,43 @@ #define VK_USE_PLATFORM_WAYLAND_KHR #endif +#define GL_KHR_vulkan_glsl + +/* Number of descriptor sets needs to be the same at alloc, */ +/* pipeline layout creation, and descriptor set layout creation */ +#define NUM_DESCRIPTOR_SETS 1 + +/* Number of samples needs to be the same at image creation, */ +/* renderpass creation and pipeline creation. */ +#define NUM_SAMPLES VK_SAMPLE_COUNT_1_BIT + +/* Number of viewports and number of scissors have to be the same */ +/* at pipeline creation and in any call to set them dynamically */ +/* They also have to be the same as each other */ +#define NUM_VIEWPORTS 1 +#define NUM_SCISSORS NUM_VIEWPORTS + +#include +//#include #include #include #include +#include #include +#include #include #include #include #include +#include +#include #include #include #include +#include +#include //LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); typedef struct StateInfo { @@ -503,7 +527,6 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int n memoryAllocInfo.allocationSize = memoryRequirements.size; - uint32_t memoryTypeIndex = 0; VkFlags requirements = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; // Search memtypes to find first index with those properties for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; i++) { @@ -516,12 +539,12 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int n memoryRequirements.memoryTypeBits >>= 1; } // No memory types matched, return failure - assert(memoryTypeIndex && "No mappable, coherent memory"); + assert(memoryAllocInfo.memoryTypeIndex && "No mappable, coherent memory"); result = vkAllocateMemory(device, &memoryAllocInfo, NULL, &(uniformData.mem)); assert(result == VK_SUCCESS); - uint8_t* pData; //VK_WHOLE_SIZE + uint8_t* pData; //VK_WHOLE_SIZE = through buffer end result = vkMapMemory(device, uniformData.mem, 0, memoryRequirements.size, 0, (void **)&pData); assert(result == VK_SUCCESS); @@ -532,11 +555,402 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int n result = vkBindBufferMemory(device, uniformData.buf, uniformData.mem, 0); assert(result == VK_SUCCESS); + + /* + * typedef struct VkDescriptorBufferInfo { + * VkBuffer buffer; + * VkDeviceSize offset; + * VkDeviceSize range; + * } VkDescriptorBufferInfo; + */ uniformData.descriptorBufferInfo.buffer = uniformData.buf; uniformData.descriptorBufferInfo.offset = 0; uniformData.descriptorBufferInfo.range = sizeof(MVP); + /* Descriptor set declaration */ + + /* Start with just our uniform buffer that has our transformation matrices + * (for the vertex shader). The fragment shader we intend to use needs no + * external resources, so nothing else is necessary + */ + + /* Note that when we start using textures, this is where our sampler will + * need to be specified + */ + VkDescriptorSetLayoutBinding descriptorSetLayoutBinding = {}; + descriptorSetLayoutBinding.binding = 0; + descriptorSetLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorSetLayoutBinding.descriptorCount = 1; + descriptorSetLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + descriptorSetLayoutBinding.pImmutableSamplers = NULL; + + /* Next take layout bindings and use them to create a descriptor set layout + */ + std::vector descriptorSetLayouts; + VkDescriptorSetLayoutCreateInfo descriptorSetInfo = {}; + descriptorSetInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + descriptorSetInfo.pNext = NULL; + descriptorSetInfo.bindingCount = 1; + descriptorSetInfo.pBindings = &descriptorSetLayoutBinding; + + descriptorSetLayouts.resize(NUM_DESCRIPTOR_SETS); + result = vkCreateDescriptorSetLayout(device, &descriptorSetInfo, NULL, descriptorSetLayouts.data()); + assert(result == VK_SUCCESS); + + /* Now use the descriptor layout to create a pipeline layout */ + //Constant ranges constant to shader not used here + VkPipelineLayout pipelineLayout; + VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.pNext = NULL; + pipelineLayoutInfo.pushConstantRangeCount = 0; + pipelineLayoutInfo.pPushConstantRanges = NULL; + pipelineLayoutInfo.setLayoutCount = NUM_DESCRIPTOR_SETS; + pipelineLayoutInfo.pSetLayouts = descriptorSetLayouts.data(); + + result = vkCreatePipelineLayout(device, &pipelineLayoutInfo, NULL, &pipelineLayout); + assert(result == VK_SUCCESS); + + + /* Descriptor set initialization */ + + VkDescriptorPoolSize typeCount[1]; + typeCount[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + typeCount[0].descriptorCount = 1; + + VkDescriptorPool descriptorPool; + VkDescriptorPoolCreateInfo descriptorPoolInfo = {}; + descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + descriptorPoolInfo.pNext = NULL; + descriptorPoolInfo.maxSets = 1; + descriptorPoolInfo.poolSizeCount = 1; + descriptorPoolInfo.pPoolSizes = typeCount; + + result = vkCreateDescriptorPool(device, &descriptorPoolInfo, NULL, &descriptorPool); + assert(result == VK_SUCCESS); + std::vector descriptorSets; + VkDescriptorSetAllocateInfo descriptorSetAllocInfo[1]; + descriptorSetAllocInfo[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + descriptorSetAllocInfo[0].pNext = NULL; + descriptorSetAllocInfo[0].descriptorPool = descriptorPool; + descriptorSetAllocInfo[0].descriptorSetCount = NUM_DESCRIPTOR_SETS; + descriptorSetAllocInfo[0].pSetLayouts = descriptorSetLayouts.data(); + + descriptorSets.resize(NUM_DESCRIPTOR_SETS); + result = vkAllocateDescriptorSets(device, descriptorSetAllocInfo, descriptorSets.data()); + assert(result == VK_SUCCESS); + + VkWriteDescriptorSet writes[1]; + writes[0] = {}; + writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writes[0].pNext = NULL; + writes[0].dstSet = descriptorSets[0]; + writes[0].descriptorCount = 1; + writes[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + writes[0].pBufferInfo = &uniformData.descriptorBufferInfo; + writes[0].dstArrayElement = 0; + writes[0].dstBinding = 0; + + vkUpdateDescriptorSets(device, 1, writes, 0, NULL); + + + /* Render pass definition */ + + // A semaphore (or fence) is required in order to acquire a + // swapchain image to prepare it for use in a render pass. + // The semaphore is normally used to hold back the rendering + // operation until the image is actually available. + // But since this sample does not render, the semaphore + // ends up being unused. + VkSemaphore imageAcquiredSemaphore; + VkSemaphoreCreateInfo imageAcquiredSemaphoreInfo; + imageAcquiredSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + imageAcquiredSemaphoreInfo.pNext = NULL; + imageAcquiredSemaphoreInfo.flags = 0; + + result = vkCreateSemaphore(device, &imageAcquiredSemaphoreInfo, NULL, &imageAcquiredSemaphore); + assert(result == VK_SUCCESS); + + // Acquire the swapchain image in order to set its layout + uint32_t currentBuffer; + result = vkAcquireNextImageKHR(device, swapchain, UINT64_MAX, imageAcquiredSemaphore, VK_NULL_HANDLE, + ¤tBuffer); + assert(result >= 0); + + // The initial layout for the color and depth attachments will be + // LAYOUT_UNDEFINED because at the start of the renderpass, we don't + // care about their contents. At the start of the subpass, the color + // attachment's layout will be transitioned to LAYOUT_COLOR_ATTACHMENT_OPTIMAL + // and the depth stencil attachment's layout will be transitioned to + // LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL. At the end of the renderpass, + // the color attachment's layout will be transitioned to + // LAYOUT_PRESENT_SRC_KHR to be ready to present. This is all done as part + // of the renderpass, no barriers are necessary. + VkAttachmentDescription attachments[1]; + attachments[0].format = format; + attachments[0].samples = NUM_SAMPLES; + attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + attachments[0].flags = 0; + + // Depth + /* + * attachments[1].format = info.depth.format; + * attachments[1].samples = NUM_SAMPLES; + * attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + * attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + * attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + * attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + * attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + * attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + * attachments[1].flags = 0; + */ + + VkAttachmentReference color_reference = {}; + color_reference.attachment = 0; + color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + /* + * VkAttachmentReference depth_reference = {}; + * depth_reference.attachment = 1; + * depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + */ + + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.flags = 0; + subpass.inputAttachmentCount = 0; + subpass.pInputAttachments = NULL; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color_reference; + subpass.pResolveAttachments = NULL; + subpass.pDepthStencilAttachment = NULL;//&depth_reference; + subpass.preserveAttachmentCount = 0; + subpass.pPreserveAttachments = NULL; + + // Subpass dependency to wait for wsi image acquired semaphore before starting layout transition + VkSubpassDependency subpassDependency = {}; + subpassDependency.srcSubpass = VK_SUBPASS_EXTERNAL; + subpassDependency.dstSubpass = 0; + subpassDependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + subpassDependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + subpassDependency.srcAccessMask = 0; + subpassDependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + subpassDependency.dependencyFlags = 0; + + VkRenderPass renderPass; + VkRenderPassCreateInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.pNext = NULL; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = attachments; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &subpassDependency; + + result = vkCreateRenderPass(device, &renderPassInfo, NULL, &renderPass); + assert(result == VK_SUCCESS); + + /* Shader assignment and creation */ + //Creation via runtime GLSL compiling + VkShaderModule vertexShader; //<- spirv bytecode + VkShaderModule fragmentShader; + + + HANDLE glslFile = CreateFileW( + L"../src/shaderv.vert", + GENERIC_READ, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL + ); + //FILE* glslFile = fopen(, "r"); + assert(glslFile); + const uint64_t SOURCE_SIZE = 1024 * 1024; + char* source = (char*)calloc(SOURCE_SIZE, sizeof(char)); + DWORD bytesRead = 0; + if (!ReadFile(glslFile, source, SOURCE_SIZE, &bytesRead, NULL)) exit(-5); + //char bytesReadString[(6* sizeof(char))]; + //itoa(bytesRead, bytesReadString, 10); + //OutputDebugStringA(bytesReadString); + OutputDebugStringA("\n"); + + glslang_initialize_process(); + glslang_stage_t stage = GLSLANG_STAGE_VERTEX; + + glslang_input_t input = { + .language = GLSLANG_SOURCE_GLSL, + .stage = stage, + .client = GLSLANG_CLIENT_VULKAN, + .client_version = GLSLANG_TARGET_VULKAN_1_1, + .target_language = GLSLANG_TARGET_SPV, + .target_language_version = GLSLANG_TARGET_SPV_1_3, + .code = source, + .default_version = 100, + .default_profile = GLSLANG_NO_PROFILE, + .force_default_version_and_profile = false, + .forward_compatible = false, + .messages = GLSLANG_MSG_DEFAULT_BIT, + .resource = glslang_default_resource() + + }; + + glslang_shader_t* shader = glslang_shader_create(&input); + OutputDebugStringA("GLSL parse faild\n"); + if (!glslang_shader_preprocess(shader, &input)) { + OutputDebugStringA("GLSL preprocessing fail\n"); + OutputDebugStringA(glslang_shader_get_info_log(shader)); + + OutputDebugStringA("\n"); + OutputDebugStringA(glslang_shader_get_info_debug_log(shader)); + OutputDebugStringA("\n"); + + OutputDebugStringA(glslang_shader_get_preprocessed_code(shader)); + OutputDebugStringA("\n"); + + exit(-6); + } + if (!glslang_shader_parse(shader, &input)) { + OutputDebugStringA("GLSL parse fail\n"); + OutputDebugStringA(glslang_shader_get_info_log(shader)); + OutputDebugStringA(glslang_shader_get_info_debug_log(shader)); + OutputDebugStringA("\n"); + OutputDebugStringA(input.code); + exit(-7); + } + glslang_program_t* program = glslang_program_create(); + glslang_program_add_shader(program, shader); + + if (!glslang_program_link(program, GLSLANG_MSG_SPV_RULES_BIT | GLSLANG_MSG_VULKAN_RULES_BIT)) { + exit(-8); + } + + glslang_program_SPIRV_generate(program, stage); + std::vector spirv; + size_t programSize = glslang_program_SPIRV_get_size(program); + spirv.resize(programSize); + glslang_program_SPIRV_get(program, spirv.data()); + + /* + * const char* spirv_messages = glslang_program_SPIRV_get_messages(program); + * if(spirv_messages) { + * fprintf(stderr, "SPIRV mes: '%s'", spirv_messages); + * } + */ + + VkShaderModuleCreateInfo shaderInfo = {}; + shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + shaderInfo.codeSize = spirv.size() * sizeof(uint32_t); + shaderInfo.pCode = (const uint32_t*)spirv.data(); + + result = vkCreateShaderModule(device, &shaderInfo, NULL, &vertexShader); + assert(result == VK_SUCCESS); + glslang_program_delete(program); + glslang_shader_delete(shader); + + glslang_finalize_process(); + + //also compile fragment shader + CloseHandle(glslFile); + glslFile = CreateFileW( + L"../src/shaderf.frag", + GENERIC_READ, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL + ); + assert(glslFile); + memset(source, 0, SOURCE_SIZE * sizeof(char)); + if (!ReadFile(glslFile, source, SOURCE_SIZE, &bytesRead, NULL)) exit(-5); + glslang_initialize_process(); + + stage = GLSLANG_STAGE_FRAGMENT; + input.stage = stage; + shader = glslang_shader_create(&input); + + if (!glslang_shader_preprocess(shader, &input)) { + OutputDebugStringA("GLSL preprocessing fail\n"); + OutputDebugStringA(glslang_shader_get_info_log(shader)); + + OutputDebugStringA("\n"); + OutputDebugStringA(glslang_shader_get_info_debug_log(shader)); + OutputDebugStringA("\n"); + + OutputDebugStringA(glslang_shader_get_preprocessed_code(shader)); + OutputDebugStringA("\n"); + + exit(-6); + } + if (!glslang_shader_parse(shader, &input)) { + OutputDebugStringA("GLSL parse fail\n"); + OutputDebugStringA(glslang_shader_get_info_log(shader)); + OutputDebugStringA(glslang_shader_get_info_debug_log(shader)); + OutputDebugStringA("\n"); + OutputDebugStringA(input.code); + exit(-7); + }; + + program = glslang_program_create(); + glslang_program_add_shader(program, shader); + + if (!glslang_program_link(program, GLSLANG_MSG_SPV_RULES_BIT | GLSLANG_MSG_VULKAN_RULES_BIT)) { + exit(-8); + } + + glslang_program_SPIRV_generate(program, stage); + programSize = glslang_program_SPIRV_get_size(program); + spirv.resize(programSize); + glslang_program_SPIRV_get(program, spirv.data()); + + /* + * const char* spirv_messages = glslang_program_SPIRV_get_messages(program); + * if(spirv_messages) { + * fprintf(stderr, "SPIRV mes: '%s'", spirv_messages); + * } + */ + + shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + shaderInfo.codeSize = spirv.size() * sizeof(uint32_t); + shaderInfo.pCode = (const uint32_t*)spirv.data(); + + result = vkCreateShaderModule(device, &shaderInfo, NULL, &fragmentShader); + assert(result == VK_SUCCESS); + glslang_program_delete(program); + glslang_shader_delete(shader); + + glslang_finalize_process(); + CloseHandle(glslFile); + free(source); + + //Pipeline setup + VkPipelineShaderStageCreateInfo shaderStages[2]; + shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shaderStages[0].pNext = NULL; + shaderStages[0].pSpecializationInfo = NULL; + shaderStages[0].flags = 0; + shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + shaderStages[0].pName = "main"; + shaderStages[0].module = vertexShader; + shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shaderStages[1].pNext = NULL; + shaderStages[1].pSpecializationInfo = NULL; + shaderStages[1].flags = 0; + shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; + shaderStages[1].pName = "main"; + shaderStages[1].module = fragmentShader; + + + //Window show and event loop @@ -548,6 +962,13 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int n DispatchMessage(&msg); } + vkDestroyShaderModule(device, shaderStages[0].module, NULL); + vkDestroyShaderModule(device, shaderStages[1].module, NULL); + vkDestroyRenderPass(device, renderPass, NULL); + vkDestroySemaphore(device, imageAcquiredSemaphore, NULL); + vkDestroyDescriptorPool(device, descriptorPool, NULL); + for (int i = 0; i < NUM_DESCRIPTOR_SETS; i++) vkDestroyDescriptorSetLayout(device, descriptorSetLayouts[i], NULL); + vkDestroyPipelineLayout(device, pipelineLayout, NULL); vkDestroyBuffer(device, uniformData.buf, NULL); vkFreeMemory(device, uniformData.mem, NULL); VkCommandBuffer cmdBufs[1] = {cmd}; diff --git a/src/shaderf.frag b/src/shaderf.frag new file mode 100644 index 0000000..5bbc26b --- /dev/null +++ b/src/shaderf.frag @@ -0,0 +1,11 @@ +#version 400 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (binding = 1) uniform sampler2D tex; +layout (location = 0) in vec2 texcoord; +layout (location = 0) out vec4 outColor; + +void main() { + outColor = textureLod(tex, texcoord, 0.0); +} diff --git a/src/shaderv.vert b/src/shaderv.vert new file mode 100644 index 0000000..96062f3 --- /dev/null +++ b/src/shaderv.vert @@ -0,0 +1,19 @@ +#version 400 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +//set = desc set layout +//binding = desc set binding +//var[I] = descriptor set pos in array +layout (std140, set = 0, binding = 0) uniform buf { + mat4 mvp; +} ubuf; + +layout (location = 0) in vec4 pos; +layout (location = 1) in vec2 inTexCoords; +layout (location = 0) out vec2 texcoord; + +void main() { + texcoord = inTexCoords; + gl_Position = ubuf.mvp * pos; +}