# HG changeset patch # User Sam # Date 1684691441 -25200 # Node ID 3b388986c7fd391b45049af0d8c383e496410f4f # Parent 66205c67df6c09783da62cc6663452ef3a2d9e4a did: refactor texture data structures, add more complete (untested) material import diff -r 66205c67df6c -r 3b388986c7fd src/semicongine/core/imagetypes.nim --- a/src/semicongine/core/imagetypes.nim Sun May 21 01:04:55 2023 +0700 +++ b/src/semicongine/core/imagetypes.nim Mon May 22 00:50:41 2023 +0700 @@ -1,10 +1,31 @@ +import ./vulkanapi + type Pixel* = array[4, uint8] ImageObject* = object width*: uint32 height*: uint32 imagedata*: seq[Pixel] + Sampler* = object + magnification*: VkFilter + minification*: VkFilter + wrapModeS*: VkSamplerAddressMode + wrapModeT*: VkSamplerAddressMode + filter*: VkFilter # TODO: replace with mag/minification + Image* = ref ImageObject + TextureObject = object + image*: Image + sampler*: Sampler + Texture* = ref TextureObject + +proc DefaultSampler*(): Sampler = + Sampler( + magnification: VK_FILTER_LINEAR, + minification: VK_FILTER_LINEAR, + wrapModeS: VK_SAMPLER_ADDRESS_MODE_REPEAT, + wrapModeT: VK_SAMPLER_ADDRESS_MODE_REPEAT, + ) proc newImage*(width, height: uint32, imagedata: seq[Pixel] = @[]): Image = assert width > 0 and height > 0 diff -r 66205c67df6c -r 3b388986c7fd src/semicongine/entity.nim --- a/src/semicongine/entity.nim Sun May 21 01:04:55 2023 +0700 +++ b/src/semicongine/entity.nim Mon May 22 00:50:41 2023 +0700 @@ -13,7 +13,7 @@ name*: string root*: Entity shaderGlobals*: Table[string, DataList] - textures*: Table[string, (seq[Image], VkFilter)] + textures*: Table[string, seq[Texture]] Entity* = ref object of RootObj name*: string @@ -42,11 +42,11 @@ func setShaderGlobalArray*[T](scene: var Scene, name: string, value: seq[T]) = setValues[T](scene.shaderGlobals[name], value) -func addTextures*(scene: var Scene, name: string, texture: seq[Image], interpolation=VK_FILTER_LINEAR) = - scene.textures[name] = (texture, interpolation) +func addTextures*(scene: var Scene, name: string, textures: seq[Texture], interpolation=VK_FILTER_LINEAR) = + scene.textures[name] = textures -func addTexture*(scene: var Scene, name: string, texture: Image, interpolation=VK_FILTER_LINEAR) = - scene.textures[name] = (@[texture], interpolation) +func addTexture*(scene: var Scene, name: string, texture: Texture) = + scene.textures[name] = @[texture] func newScene*(name: string, root: Entity): Scene = Scene(name: name, root: root) diff -r 66205c67df6c -r 3b388986c7fd src/semicongine/renderer.nim --- a/src/semicongine/renderer.nim Sun May 21 01:04:55 2023 +0700 +++ b/src/semicongine/renderer.nim Mon May 22 00:50:41 2023 +0700 @@ -24,7 +24,7 @@ vertexBuffers*: Table[MemoryPerformanceHint, Buffer] indexBuffer*: Buffer uniformBuffers*: seq[Buffer] # one per frame-in-flight - textures*: Table[string, seq[Texture]] # per frame-in-flight + textures*: Table[string, seq[VulkanTexture]] # per frame-in-flight attributeLocation*: Table[string, MemoryPerformanceHint] attributeBindingNumber*: Table[string, int] transformAttribute: string # name of attribute that is used for per-instance mesh transformation @@ -176,11 +176,10 @@ requireMappable=true, preferVRAM=true, ) - for name, images in scene.textures.pairs: + for name, textures in scene.textures.pairs: data.textures[name] = @[] - let interpolation = images[1] - for image in images[0]: - data.textures[name].add renderer.device.createTexture(image.width, image.height, 4, addr image.imagedata[0][0], interpolation) + for texture in textures: + data.textures[name].add renderer.device.uploadTexture(texture) data.descriptorSets[pipeline.vk] = pipeline.setupDescriptors(data.uniformBuffers, data.textures, inFlightFrames=renderer.swapchain.inFlightFrames) for frame_i in 0 ..< renderer.swapchain.inFlightFrames: data.descriptorSets[pipeline.vk][frame_i].writeDescriptorSet() diff -r 66205c67df6c -r 3b388986c7fd src/semicongine/resources/mesh.nim --- a/src/semicongine/resources/mesh.nim Sun May 21 01:04:55 2023 +0700 +++ b/src/semicongine/resources/mesh.nim Mon May 22 00:50:41 2023 +0700 @@ -10,6 +10,8 @@ import ../mesh import ../core +import ./image + type glTFHeader = object @@ -19,6 +21,21 @@ glTFData = object structuredContent: JsonNode binaryBufferData: seq[uint8] + glTFMaterial = object + color: Vec4f + colorTexture: Texture + colorTextureIndex: uint32 + metallic: float32 + roughness: float32 + metallicRoughnessTexture: Texture + metallicRoughnessTextureIndex: uint32 + normalTexture: Texture + normalTextureIndex: uint32 + occlusionTexture: Texture + occlusionTextureIndex: uint32 + emissiveTexture: Texture + emissiveTextureIndex: uint32 + emissiveFactor: Vec3f const JSON_CHUNK = 0x4E4F534A @@ -31,6 +48,19 @@ 5125: UInt32, 5126: Float32, }.toTable + SAMPLER_FILTER_MODE_MAP = { + 9728: VK_FILTER_NEAREST, + 9729: VK_FILTER_LINEAR, + 9984: VK_FILTER_NEAREST, + 9985: VK_FILTER_LINEAR, + 9986: VK_FILTER_NEAREST, + 9987: VK_FILTER_LINEAR, + }.toTable + SAMPLER_WRAP_MODE_MAP = { + 33071: VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + 33648: VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, + 10497: VK_SAMPLER_ADDRESS_MODE_REPEAT + }.toTable func getGPUType(accessor: JsonNode): DataType = # TODO: no full support for all datatypes that glTF may provide @@ -68,6 +98,16 @@ of Float32: return Vec4F32 else: raise newException(Exception, &"Unsupported data type: {componentType} {theType}") +proc getBufferViewData(bufferView: JsonNode, mainBuffer: var seq[uint8], baseBufferOffset=0): seq[uint8] = + assert bufferView["buffer"].getInt() == 0, "Currently no external buffers supported" + + result = newSeq[uint8](bufferView["byteLength"].getInt()) + let bufferOffset = bufferView["byteOffset"].getInt() + baseBufferOffset + var dstPointer = addr result[0] + + if bufferView.hasKey("byteStride"): + raise newException(Exception, "Unsupported feature: byteStride in buffer view") + copyMem(dstPointer, addr mainBuffer[bufferOffset], result.len) proc getAccessorData(root: JsonNode, accessor: JsonNode, mainBuffer: var seq[uint8]): DataList = result = newDataList(thetype=accessor.getGPUType()) @@ -154,7 +194,6 @@ # prepare mesh attributes for attribute, accessor in meshNode["primitives"][0]["attributes"].pairs: result.setMeshData(attribute.toLowerAscii, newDataList(thetype=root["accessors"][accessor.getInt()].getGPUType())) - # if meshNode["primitives"][0].hasKey("material"): result.setMeshData("material", newDataList(thetype=getDataType[uint8]())) # add all mesh data @@ -215,24 +254,73 @@ newScene(scenenode["name"].getStr(), rootEntity) -proc getMaterialsData(root: JsonNode): seq[Vec4f] = - for materialNode in root["materials"]: - let pbr = materialNode["pbrMetallicRoughness"] - var baseColor = newVec4f(1, 1, 1, 1) - if pbr.hasKey("baseColorFactor"): - baseColor[0] = pbr["baseColorFactor"][0].getFloat() - baseColor[1] = pbr["baseColorFactor"][1].getFloat() - baseColor[2] = pbr["baseColorFactor"][2].getFloat() - baseColor[3] = pbr["baseColorFactor"][3].getFloat() - result.add baseColor - # TODO: pbr["baseColorTexture"] - # TODO: pbr["metallicRoughnessTexture"] - # TODO: pbr["metallicFactor"] - # TODO: pbr["roughnessFactor"] - # TODO: materialNode["normalTexture"] - # TODO: materialNode["occlusionTexture"] - # TODO: materialNode["emissiveTexture"] - # TODO: materialNode["emissiveFactor"] +proc loadImage(root: JsonNode, imageIndex: int, mainBuffer: var seq[uint8]): Image = + if root["images"][imageIndex].hasKey("uri"): + raise newException(Exception, "Unsupported feature: Load images from external files") + + let bufferView = root["bufferViews"][root["images"][imageIndex]["bufferView"].getInt()] + let imgData = newStringStream(cast[string](getBufferViewData(bufferView, mainBuffer))) + + let imageType = root["images"][imageIndex]["mimeType"].getStr() + case imageType + of "image/bmp": + result = readBMP(imgData) + of "image/png": + result = readPNG(imgData) + else: + raise newException(Exception, "Unsupported feature: Load image of type " & imageType) + +proc loadTexture(root: JsonNode, textureIndex: int, mainBuffer: var seq[uint8]): Texture = + result = new Texture + let textureNode = root["textures"][textureIndex] + result.image = loadImage(root, textureNode["source"].getInt(), mainBuffer) + result.sampler = DefaultSampler() + + if textureNode.hasKey("sampler"): + let sampler = root["samplers"][textureNode["sampler"].getInt()] + if sampler.hasKey("magFilter"): + result.sampler.magnification = SAMPLER_FILTER_MODE_MAP[sampler["magFilter"].getInt()] + if sampler.hasKey("minFilter"): + result.sampler.minification = SAMPLER_FILTER_MODE_MAP[sampler["minFilter"].getInt()] + if sampler.hasKey("wrapS"): + result.sampler.wrapModeS = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()] + if sampler.hasKey("wrapT"): + result.sampler.wrapModeT = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()] + +proc loadMaterial(root: JsonNode, materialNode: JsonNode, mainBuffer: var seq[uint8]): glTFMaterial = + let defaultMaterial = glTFMaterial(color: newVec4f(1, 1, 1, 1)) + result = defaultMaterial + let pbr = materialNode["pbrMetallicRoughness"] + if pbr.hasKey("baseColorFactor"): + result.color[0] = pbr["baseColorFactor"][0].getFloat() + result.color[1] = pbr["baseColorFactor"][1].getFloat() + result.color[2] = pbr["baseColorFactor"][2].getFloat() + result.color[3] = pbr["baseColorFactor"][3].getFloat() + if pbr.hasKey("baseColorTexture"): + result.colorTexture = loadTexture(root, pbr["baseColorTexture"]["index"].getInt(), mainBuffer) + result.colorTextureIndex = pbr["baseColorTexture"].getOrDefault("texCoord").getInt(0).uint32 + if pbr.hasKey("metallicRoughnessTexture"): + result.metallicRoughnessTexture = loadTexture(root, pbr["metallicRoughnessTexture"]["index"].getInt(), mainBuffer) + result.metallicRoughnessTextureIndex = pbr["metallicRoughnessTexture"].getOrDefault("texCoord").getInt().uint32 + if pbr.hasKey("metallicFactor"): + result.metallic = pbr["metallicFactor"].getFloat() + if pbr.hasKey("roughnessFactor"): + result.roughness= pbr["roughnessFactor"].getFloat() + + if materialNode.hasKey("normalTexture"): + result.normalTexture = loadTexture(root, materialNode["normalTexture"]["index"].getInt(), mainBuffer) + result.metallicRoughnessTextureIndex = materialNode["normalTexture"].getOrDefault("texCoord").getInt().uint32 + if materialNode.hasKey("occlusionTexture"): + result.occlusionTexture = loadTexture(root, materialNode["occlusionTexture"]["index"].getInt(), mainBuffer) + result.occlusionTextureIndex = materialNode["occlusionTexture"].getOrDefault("texCoord").getInt().uint32 + if materialNode.hasKey("emissiveTexture"): + result.emissiveTexture = loadTexture(root, materialNode["emissiveTexture"]["index"].getInt(), mainBuffer) + result.occlusionTextureIndex = materialNode["emissiveTexture"].getOrDefault("texCoord").getInt().uint32 + if materialNode.hasKey("roughnessFactor"): + result.roughness = materialNode["roughnessFactor"].getFloat() + if materialNode.hasKey("emissiveFactor"): + let em = materialNode["emissiveFactor"] + result.emissiveFactor = newVec3f(em[0].getFloat(), em[1].getFloat(), em[2].getFloat()) proc readglTF*(stream: Stream): seq[Scene] = var @@ -265,6 +353,62 @@ for scene in data.structuredContent["scenes"]: var scene = data.structuredContent.loadScene(scene, data.binaryBufferData) - scene.addShaderGlobalArray("material_colors", getMaterialsData(data.structuredContent)) + var + color: seq[Vec4f] + colorTexture: seq[Texture] + colorTextureIndex: seq[uint32] + metallic: seq[float32] + roughness: seq[float32] + metallicRoughnessTexture: seq[Texture] + metallicRoughnessTextureIndex: seq[uint32] + normalTexture: seq[Texture] + normalTextureIndex: seq[uint32] + occlusionTexture: seq[Texture] + occlusionTextureIndex: seq[uint32] + emissiveTexture: seq[Texture] + emissiveTextureIndex: seq[uint32] + emissiveFactor: seq[Vec3f] + for materialNode in data.structuredContent["materials"]: + let m = loadMaterial(data.structuredContent, materialNode, data.binaryBufferData) + color.add m.color + if not m.colorTexture.isNil: + colorTexture.add m.colorTexture + colorTextureIndex.add m.colorTextureIndex + metallic.add m.metallic + roughness.add m.roughness + if not m.metallicRoughnessTexture.isNil: + metallicRoughnessTexture.add m.metallicRoughnessTexture + metallicRoughnessTextureIndex.add m.metallicRoughnessTextureIndex + if not m.normalTexture.isNil: + normalTexture.add m.normalTexture + normalTextureIndex.add m.normalTextureIndex + if not m.occlusionTexture.isNil: + occlusionTexture.add m.occlusionTexture + occlusionTextureIndex.add m.occlusionTextureIndex + if not m.emissiveTexture.isNil: + emissiveTexture.add m.emissiveTexture + emissiveTextureIndex.add m.emissiveTextureIndex + emissiveFactor.add m.emissiveFactor + + # material constants + if color.len > 0: scene.addShaderGlobalArray("material_color", color) + if colorTextureIndex.len > 0: scene.addShaderGlobalArray("material_color_texture_index", colorTextureIndex) + if metallic.len > 0: scene.addShaderGlobalArray("material_metallic", metallic) + if roughness.len > 0: scene.addShaderGlobalArray("material_roughness", roughness) + if metallicRoughnessTextureIndex.len > 0: scene.addShaderGlobalArray("material_metallic_roughness_texture_index", metallicRoughnessTextureIndex) + if normalTextureIndex.len > 0: scene.addShaderGlobalArray("material_normal_texture_index", normalTextureIndex) + if occlusionTextureIndex.len > 0: scene.addShaderGlobalArray("material_occlusion_texture_index", occlusionTextureIndex) + if emissiveTextureIndex.len > 0: scene.addShaderGlobalArray("material_emissive_texture_index", emissiveTextureIndex) + if emissiveFactor.len > 0: scene.addShaderGlobalArray("material_emissive_factor", emissiveFactor) + + # texture + #[ + if colorTexture.len > 0: scene.addShaderGlobalArray("material_color_texture", colorTexture) + if metallicRoughnessTexture.len > 0: scene.addShaderGlobalArray("material_metallic_roughness_texture", metallicRoughnessTexture) + if normalTexture.len > 0: scene.addShaderGlobalArray("material_normal_texture", normalTexture) + if occlusionTexture.len > 0: scene.addShaderGlobalArray("material_occlusion_texture", occlusionTexture) + if emissiveTexture.len > 0: scene.addShaderGlobalArray("material_emissive_texture", emissiveTexture) + ]# + result.add scene diff -r 66205c67df6c -r 3b388986c7fd src/semicongine/vulkan/descriptor.nim --- a/src/semicongine/vulkan/descriptor.nim Sun May 21 01:04:55 2023 +0700 +++ b/src/semicongine/vulkan/descriptor.nim Mon May 22 00:50:41 2023 +0700 @@ -20,7 +20,7 @@ size*: uint64 of ImageSampler: imageviews*: seq[ImageView] - samplers*: seq[Sampler] + samplers*: seq[VulkanSampler] DescriptorSet* = object # "instance" of a DescriptorSetLayout vk*: VkDescriptorSet layout*: DescriptorSetLayout diff -r 66205c67df6c -r 3b388986c7fd src/semicongine/vulkan/image.nim --- a/src/semicongine/vulkan/image.nim Sun May 21 01:04:55 2023 +0700 +++ b/src/semicongine/vulkan/image.nim Mon May 22 00:50:41 2023 +0700 @@ -10,7 +10,7 @@ type PixelDepth = 1'u32 .. 4'u32 - GPUImage* = object + VulkanImage* = object device*: Device vk*: VkImage width*: uint32 # pixel @@ -22,16 +22,16 @@ of false: discard of true: memory*: DeviceMemory - Sampler* = object + VulkanSampler* = object device*: Device vk*: VkSampler ImageView* = object vk*: VkImageView - image*: GPUImage - Texture* = object - image*: GPUImage + image*: VulkanImage + VulkanTexture* = object + image*: VulkanImage imageView*: ImageView - sampler*: Sampler + sampler*: VulkanSampler const DEPTH_FORMAT_MAP = { PixelDepth(1): VK_FORMAT_R8_SRGB, @@ -41,7 +41,7 @@ }.toTable -proc requirements(image: GPUImage): MemoryRequirements = +proc requirements(image: VulkanImage): MemoryRequirements = assert image.vk.valid assert image.device.vk.valid var req: VkMemoryRequirements @@ -53,7 +53,7 @@ if ((req.memoryTypeBits shr i) and 1) == 1: result.memoryTypes.add memorytypes[i] -proc allocateMemory(image: var GPUImage, requireMappable: bool, preferVRAM: bool, preferAutoFlush: bool) = +proc allocateMemory(image: var VulkanImage, requireMappable: bool, preferVRAM: bool, preferAutoFlush: bool) = assert image.device.vk.valid assert image.memoryAllocated == false @@ -68,7 +68,7 @@ image.memory = image.device.allocate(requirements.size, memoryType) checkVkResult image.device.vk.vkBindImageMemory(image.vk, image.memory.vk, VkDeviceSize(0)) -proc transitionImageLayout*(image: GPUImage, oldLayout, newLayout: VkImageLayout) = +proc transitionImageLayout*(image: VulkanImage, oldLayout, newLayout: VkImageLayout) = var barrier = VkImageMemoryBarrier( sType: VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, oldLayout: oldLayout, @@ -109,7 +109,7 @@ 1, addr barrier ) -proc copy*(src: Buffer, dst: GPUImage) = +proc copy*(src: Buffer, dst: VulkanImage) = assert src.device.vk.valid assert dst.device.vk.valid assert src.device == dst.device @@ -139,7 +139,7 @@ ) # currently only usable for texture access from shader -proc createImage*(device: Device, width, height: uint32, depth: PixelDepth, data: pointer): GPUImage = +proc createImage*(device: Device, width, height: uint32, depth: PixelDepth, data: pointer): VulkanImage = assert device.vk.valid assert width > 0 assert height > 0 @@ -177,7 +177,7 @@ result.transitionImageLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) stagingBuffer.destroy() -proc destroy*(image: var GPUImage) = +proc destroy*(image: var VulkanImage) = assert image.device.vk.valid assert image.vk.valid image.device.vk.vkDestroyImage(image.vk, nil) @@ -187,14 +187,14 @@ image.memoryAllocated = false image.vk.reset -proc createSampler*(device: Device, interpolation: VkFilter): Sampler = +proc createSampler*(device: Device, sampler: Sampler): VulkanSampler = assert device.vk.valid var samplerInfo = VkSamplerCreateInfo( sType: VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, - magFilter: interpolation, - minFilter: interpolation, - addressModeU: VK_SAMPLER_ADDRESS_MODE_REPEAT, - addressModeV: VK_SAMPLER_ADDRESS_MODE_REPEAT, + magFilter: sampler.magnification, + minFilter: sampler.minification, + addressModeU: sampler.wrapModeS, + addressModeV: sampler.wrapModeT, addressModeW: VK_SAMPLER_ADDRESS_MODE_REPEAT, anisotropyEnable: device.enabledFeatures.samplerAnisotropy, maxAnisotropy: device.physicalDevice.properties.limits.maxSamplerAnisotropy, @@ -210,14 +210,14 @@ result.device = device checkVkResult device.vk.vkCreateSampler(addr samplerInfo, nil, addr result.vk) -proc destroy*(sampler: var Sampler) = +proc destroy*(sampler: var VulkanSampler) = assert sampler.device.vk.valid assert sampler.vk.valid sampler.device.vk.vkDestroySampler(sampler.vk, nil) sampler.vk.reset proc createImageView*( - image: GPUImage, + image: VulkanImage, imageviewtype=VK_IMAGE_VIEW_TYPE_2D, baseMipLevel=0'u32, levelCount=1'u32, @@ -255,14 +255,13 @@ imageview.image.device.vk.vkDestroyImageView(imageview.vk, nil) imageview.vk.reset() -proc createTexture*(device: Device, width, height: uint32, depth: PixelDepth, data: pointer, interpolation: VkFilter): Texture = +proc uploadTexture*(device: Device, texture: Texture): VulkanTexture = assert device.vk.valid - - result.image = createImage(device=device, width=width, height=height, depth=depth, data=data) + result.image = createImage(device=device, width=texture.image.width, height=texture.image.height, depth=4, data=addr texture.image.imagedata[0][0]) result.imageView = result.image.createImageView() - result.sampler = result.image.device.createSampler(interpolation) + result.sampler = result.image.device.createSampler(texture.sampler) -proc destroy*(texture: var Texture) = +proc destroy*(texture: var VulkanTexture) = texture.image.destroy() texture.imageView.destroy() texture.sampler.destroy() diff -r 66205c67df6c -r 3b388986c7fd src/semicongine/vulkan/pipeline.nim --- a/src/semicongine/vulkan/pipeline.nim Sun May 21 01:04:55 2023 +0700 +++ b/src/semicongine/vulkan/pipeline.nim Mon May 22 00:50:41 2023 +0700 @@ -32,7 +32,7 @@ result.add attribute visitedUniforms[attribute.name] = attribute -proc setupDescriptors*(pipeline: var Pipeline, buffers: seq[Buffer], textures: Table[string, seq[Texture]], inFlightFrames: int): seq[DescriptorSet] = +proc setupDescriptors*(pipeline: var Pipeline, buffers: seq[Buffer], textures: Table[string, seq[VulkanTexture]], inFlightFrames: int): seq[DescriptorSet] = assert pipeline.vk.valid assert buffers.len == 0 or buffers.len == inFlightFrames # need to guard against this in case we have no uniforms, then we also create no buffers diff -r 66205c67df6c -r 3b388986c7fd src/semicongine/vulkan/swapchain.nim --- a/src/semicongine/vulkan/swapchain.nim Sun May 21 01:04:55 2023 +0700 +++ b/src/semicongine/vulkan/swapchain.nim Mon May 22 00:50:41 2023 +0700 @@ -112,7 +112,7 @@ var images = newSeq[VkImage](nImages) checkVkResult device.vk.vkGetSwapchainImagesKHR(swapChain.vk, addr(nImages), images.toCPointer) for vkimage in images: - let image = GPUImage(vk: vkimage, format: surfaceFormat.format, device: device) + let image = VulkanImage(vk: vkimage, format: surfaceFormat.format, device: device) let imageview = image.createImageView() swapChain.imageviews.add imageview swapChain.framebuffers.add swapchain.device.createFramebuffer(renderPass, [imageview], swapchain.dimension)