# HG changeset patch # User Sam # Date 1683305150 -25200 # Node ID be6e0f89645a62b8edb42009df0dd45a8d8eeb59 # Parent c774b064def596a7068a5619f8091fc6cf136546 add: initial code for texture support, not finished, had to completely refactor how to handle material-data (ie scene-wide data, sorry if you ever read this diff -r c774b064def5 -r be6e0f89645a src/semicongine/engine.nim --- a/src/semicongine/engine.nim Thu May 04 23:44:15 2023 +0700 +++ b/src/semicongine/engine.nim Fri May 05 23:45:50 2023 +0700 @@ -102,12 +102,12 @@ assert engine.state != Destroyed engine.renderer = engine.device.initRenderer(renderPass) -proc addScene*(engine: var Engine, scene: Entity, vertexInput: seq[ShaderAttribute], transformAttribute="") = +proc addScene*(engine: var Engine, scene: Scene, vertexInput: seq[ShaderAttribute], transformAttribute="") = assert engine.state != Destroyed assert transformAttribute == "" or transformAttribute in map(vertexInput, proc(a: ShaderAttribute): string = a.name) engine.renderer.setupDrawableBuffers(scene, vertexInput, transformAttribute=transformAttribute) -proc renderScene*(engine: var Engine, scene: Entity) = +proc renderScene*(engine: var Engine, scene: var Scene) = assert engine.state == Running assert engine.renderer.valid if engine.state == Running: diff -r c774b064def5 -r be6e0f89645a src/semicongine/entity.nim --- a/src/semicongine/entity.nim Thu May 04 23:44:15 2023 +0700 +++ b/src/semicongine/entity.nim Fri May 05 23:45:50 2023 +0700 @@ -1,14 +1,22 @@ import std/strformat +import std/tables import std/hashes import std/typetraits import ./math/matrix import ./gpu_data +import ./vulkan/image type Component* = ref object of RootObj entity*: Entity + Scene* = object + name*: string + root*: Entity + shaderGlobals*: Table[string, DataValue] + texture: Table[string, TextureImage] + Entity* = ref object of RootObj name*: string transform*: Mat4 # todo: cache transform + only update VBO when transform changed @@ -16,17 +24,31 @@ children*: seq[Entity] components*: seq[Component] - ShaderGlobal* = ref object of Component + TextureImage* = ref object of RootObj name*: string - value*: DataValue + width*: int + height*: int + imagedata*: seq[array[4, uint8]] -func `$`*(global: ShaderGlobal): string = - &"ShaderGlobal(name: {global.name}, {global.value})" - -func initShaderGlobal*[T](name: string, data: T): ShaderGlobal = +func addShaderGlobal*[T](scene: var Scene, name: string, data: T) = var value = DataValue(thetype: getDataType[T]()) value.setValue(data) - ShaderGlobal(name: name, value: value) + scene.shaderGlobals[name] = value + +func getShaderGlobal*(scene: Scene, name: string): DataValue = + return scene.shaderGlobals[name] + +func addTexture*[T](scene: var Scene, name: string, texture: Texture) = + scene.textures[name] = texture + +func newScene*(name: string, root: Entity): Scene = + Scene(name: name, root: root) + +func hash*(scene: Scene): Hash = + hash(scene.name) + +func `==`*(a, b: Scene): bool = + a.name == b.name func hash*(entity: Entity): Hash = hash(cast[pointer](entity)) diff -r c774b064def5 -r be6e0f89645a src/semicongine/gpu_data.nim --- a/src/semicongine/gpu_data.nim Thu May 04 23:44:15 2023 +0700 +++ b/src/semicongine/gpu_data.nim Fri May 05 23:45:50 2023 +0700 @@ -6,7 +6,8 @@ import ./math type - GPUType* = float32 | float64 | int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 | TVec2[int32] | TVec2[int64] | TVec3[int32] | TVec3[int64] | TVec4[int32] | TVec4[int64] | TVec2[uint32] | TVec2[uint64] | TVec3[uint32] | TVec3[uint64] | TVec4[uint32] | TVec4[uint64] | TVec2[float32] | TVec2[float64] | TVec3[float32] | TVec3[float64] | TVec4[float32] | TVec4[float64] | TMat2[float32] | TMat2[float64] | TMat23[float32] | TMat23[float64] | TMat32[float32] | TMat32[float64] | TMat3[float32] | TMat3[float64] | TMat34[float32] | TMat34[float64] | TMat43[float32] | TMat43[float64] | TMat4[float32] | TMat4[float64] + Sampler2DType* = object + GPUType* = float32 | float64 | int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 | TVec2[int32] | TVec2[int64] | TVec3[int32] | TVec3[int64] | TVec4[int32] | TVec4[int64] | TVec2[uint32] | TVec2[uint64] | TVec3[uint32] | TVec3[uint64] | TVec4[uint32] | TVec4[uint64] | TVec2[float32] | TVec2[float64] | TVec3[float32] | TVec3[float64] | TVec4[float32] | TVec4[float64] | TMat2[float32] | TMat2[float64] | TMat23[float32] | TMat23[float64] | TMat32[float32] | TMat32[float64] | TMat3[float32] | TMat3[float64] | TMat34[float32] | TMat34[float64] | TMat43[float32] | TMat43[float64] | TMat4[float32] | TMat4[float64] | Sampler2DType DataType* = enum Float32 Float64 @@ -50,6 +51,7 @@ Mat43F64 Mat4F32 Mat4F64 + Sampler2D DataValue* = object case thetype*: DataType of Float32: float32: float32 @@ -94,6 +96,7 @@ of Mat43F64: mat43f64: TMat43[float64] of Mat4F32: mat4f32: TMat4[float32] of Mat4F64: mat4f64: TMat4[float64] + of Sampler2D: discard DataList* = object len*: uint32 case thetype*: DataType @@ -139,6 +142,7 @@ of Mat43F64: mat43f64: seq[TMat43[float64]] of Mat4F32: mat4f32*: seq[TMat4[float32]] of Mat4F64: mat4f64: seq[TMat4[float64]] + of Sampler2D: discard MemoryPerformanceHint* = enum PreferFastRead, PreferFastWrite ShaderAttribute* = object @@ -157,7 +161,6 @@ if attr.perInstance == false: result.add attr - func numberOfVertexInputAttributeDescriptors*(thetype: DataType): uint32 = case thetype: of Mat2F32, Mat2F64, Mat23F32, Mat23F64: 2 @@ -209,6 +212,7 @@ of Mat43F64: 92 of Mat4F32: 64 of Mat4F64: 128 + of Sampler2D: 0 func size*(attribute: ShaderAttribute, perDescriptor=false): uint32 = if perDescriptor: attribute.thetype.size div attribute.thetype.numberOfVertexInputAttributeDescriptors @@ -273,6 +277,7 @@ elif T is TMat43[float64]: Mat43F64 elif T is TMat4[float32]: Mat4F32 elif T is TMat4[float64]: Mat4F64 + elif T is Sampler2DType: Sampler2D else: static: raise newException(Exception, &"Unsupported data type for GPU data: {name(T)}" ) @@ -338,6 +343,7 @@ elif T is TMat43[float64]: value.mat43f64 elif T is TMat4[float32]: value.mat4f32 elif T is TMat4[float64]: value.mat4f64 + else: {.error: "Virtual datatype has no value" .} func get*[T: GPUType|int|uint|float](value: DataList): seq[T] = when T is float32: value.float32 @@ -388,6 +394,7 @@ elif T is TMat43[float64]: value.mat43f64 elif T is TMat4[float32]: value.mat4f32 elif T is TMat4[float64]: value.mat4f64 + else: {. error: "Virtual datatype has no values" .} func getRawData*(value: var DataValue): (pointer, uint32) = result[1] = value.thetype.size @@ -434,6 +441,7 @@ of Mat43F64: result[0] = addr value.mat43f64 of Mat4F32: result[0] = addr value.mat4f32 of Mat4F64: result[0] = addr value.mat4f64 + of Sampler2D: result[0] = nil func getRawData*(value: var DataList): (pointer, uint32) = result[1] = value.thetype.size * value.len @@ -480,6 +488,7 @@ of Mat43F64: result[0] = addr value.mat43f64[0] of Mat4F32: result[0] = addr value.mat4f32[0] of Mat4F64: result[0] = addr value.mat4f64[0] + of Sampler2D: result[0] = nil func initData*(value: var DataList, len: uint32) = value.len = len @@ -526,6 +535,7 @@ of Mat43F64: value.mat43f64.setLen(len) of Mat4F32: value.mat4f32.setLen(len) of Mat4F64: value.mat4f64.setLen(len) + of Sampler2D: discard func setValue*[T: GPUType|int|uint|float](value: var DataValue, data: T) = when T is float32: value.float32 = data @@ -576,6 +586,7 @@ elif T is TMat43[float64]: value.mat43f64 = data elif T is TMat4[float32]: value.mat4f32 = data elif T is TMat4[float64]: value.mat4f64 = data + else: {.error: "Virtual datatype has no value" .} func setValues*[T: GPUType|int|uint|float](value: var DataList, data: seq[T]) = value.len = uint32(data.len) @@ -627,6 +638,7 @@ elif T is TMat43[float64]: value.mat43f64 = data elif T is TMat4[float32]: value.mat4f32 = data elif T is TMat4[float64]: value.mat4f64 = data + else: {. error: "Virtual datatype has no values" .} func setValue*[T: GPUType|int|uint|float](value: var DataList, i: uint32, data: T) = assert i < value.len @@ -678,6 +690,7 @@ elif T is TMat43[float64]: value.mat43f64[i] = data elif T is TMat4[float32]: value.mat4f32[i] = data elif T is TMat4[float64]: value.mat4f64[i] = data + else: {. error: "Virtual datatype has no values" .} const TYPEMAP = { Float32: VK_FORMAT_R32_SFLOAT, @@ -780,6 +793,7 @@ of Mat43F64: 2 of Mat4F32: 1 of Mat4F64: 2 + of Sampler2D: 1 func glslType*(thetype: DataType): string = # todo: likely not correct as we would need to enable some @@ -823,6 +837,7 @@ of Mat43F64: "dmat43" of Mat4F32: "mat4" of Mat4F64: "dmat4" + of Sampler2D: "sampler2D" func glslInput*(group: seq[ShaderAttribute]): seq[string] = if group.len == 0: @@ -833,7 +848,7 @@ for j in 0 ..< attribute.thetype.numberOfVertexInputAttributeDescriptors: i += attribute.thetype.nLocationSlots -func glslUniforms*(group: seq[ShaderAttribute], blockName="Uniforms", binding=0): seq[string] = +func glslUniforms*(group: seq[ShaderAttribute], blockName="Uniforms", binding: int): seq[string] = if group.len == 0: return @[] # currently only a single uniform block supported, therefore binding = 0 @@ -842,6 +857,14 @@ result.add(&" {attribute.thetype.glslType} {attribute.name};") result.add(&"}} {blockName};") +func glslSamplers*(group: seq[ShaderAttribute], basebinding: int): seq[string] = + if group.len == 0: + return @[] + var thebinding = basebinding + for attribute in group: + result.add(&"layout(binding = {thebinding}) uniform {attribute.thetype.glslType} {attribute.name};") + inc thebinding + func glslOutput*(group: seq[ShaderAttribute]): seq[string] = if group.len == 0: return @[] diff -r c774b064def5 -r be6e0f89645a src/semicongine/renderer.nim --- a/src/semicongine/renderer.nim Thu May 04 23:44:15 2023 +0700 +++ b/src/semicongine/renderer.nim Fri May 05 23:45:50 2023 +0700 @@ -11,6 +11,8 @@ import ./vulkan/physicaldevice import ./vulkan/renderpass import ./vulkan/swapchain +import ./vulkan/descriptor +import ./vulkan/image import ./entity import ./mesh @@ -22,6 +24,9 @@ drawables*: OrderedTable[Mesh, Drawable] vertexBuffers*: Table[MemoryPerformanceHint, Buffer] indexBuffer*: Buffer + uniformBuffers*: seq[Buffer] # one per frame-in-flight + images*: seq[Image] # used to back texturees + textures*: seq[Table[string, Texture]] # 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 @@ -31,7 +36,7 @@ surfaceFormat: VkSurfaceFormatKHR renderPass: RenderPass swapchain: Swapchain - scenedata: Table[Entity, SceneData] + scenedata: Table[Scene, SceneData] proc initRenderer*(device: Device, renderPass: RenderPass): Renderer = @@ -47,10 +52,11 @@ raise newException(Exception, "Unable to create swapchain") result.swapchain = swapchain.get() -proc setupDrawableBuffers*(renderer: var Renderer, scene: Entity, inputs: seq[ShaderAttribute], transformAttribute="") = +proc setupDrawableBuffers*(renderer: var Renderer, scene: Scene, inputs: seq[ShaderAttribute], transformAttribute="") = assert not (scene in renderer.scenedata) var data = SceneData() + # when mesh transformation are handled through the scenegraph-transformation, set it up here if transformattribute != "": var hasTransformAttribute = false for input in inputs: @@ -61,14 +67,15 @@ assert hasTransformAttribute data.transformAttribute = transformAttribute - + # find all meshes, populate missing attribute values for shader var allMeshes: seq[Mesh] - for mesh in allComponentsOfType[Mesh](scene): + for mesh in allComponentsOfType[Mesh](scene.root): allMeshes.add mesh for inputAttr in inputs: if not mesh.hasDataFor(inputAttr.name): mesh.initData(inputAttr) + # create index buffer if necessary var indicesBufferSize = 0'u64 for mesh in allMeshes: if mesh.indexType != None: @@ -89,7 +96,8 @@ preferVRAM=true, ) - # one vertex data buffer per memory location + # create vertex buffers and calculcate offsets + # trying to use one buffer per memory type var perLocationOffsets: Table[MemoryPerformanceHint, uint64] perLocationSizes: Table[MemoryPerformanceHint, uint64] @@ -113,6 +121,7 @@ ) perLocationOffsets[memoryPerformanceHint] = 0 + # fill vertex buffers var indexBufferOffset = 0'u64 for mesh in allMeshes: var offsets: seq[(string, MemoryPerformanceHint, uint64)] @@ -145,6 +154,28 @@ indexBufferOffset += size data.drawables[mesh] = drawable + # setup uniforms and textures + for i in 0 ..< renderer.renderPass.subpasses.len: + var subpass = renderer.renderPass.subpasses[i] + for pipeline in subpass.pipelines.mitems: + var uniformBufferSize = 0'u64 + for uniform in pipeline.uniforms: + uniformBufferSize += uniform.thetype.size + if uniformBufferSize > 0: + for i in 0 ..< renderer.swapchain.inFlightFrames: + data.uniformBuffers.add renderer.device.createBuffer( + size=uniformBufferSize, + usage=[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT], + requireMappable=true, + preferVRAM=true, + ) + # textures: seq[Table[string, Texture]] + var textures: Table[string, Texture] + data.textures.add textures + # need a separate descriptor for each frame in flight + pipeline.setupDescriptors(data.uniformBuffers, data.textures, inFlightFrames=renderer.swapchain.inFlightFrames) + pipeline.descriptorSets[i].writeDescriptorSet() + renderer.scenedata[scene] = data proc refreshMeshAttributeData(sceneData: var SceneData, mesh: Mesh, attribute: string) = @@ -154,11 +185,10 @@ let bindingNumber = sceneData.attributeBindingNumber[attribute] sceneData.vertexBuffers[memoryPerformanceHint].setData(pdata, size, sceneData.drawables[mesh].bufferOffsets[bindingNumber][2]) - -proc refreshMeshData*(renderer: var Renderer, scene: Entity) = +proc refreshMeshData*(renderer: var Renderer, scene: Scene) = assert scene in renderer.scenedata - for mesh in allComponentsOfType[Mesh](scene): + for mesh in allComponentsOfType[Mesh](scene.root): # if mesh transformation attribute is enabled, update the model matrix if renderer.scenedata[scene].transformAttribute != "": let transform = mesh.entity.getModelTransform() @@ -173,9 +203,24 @@ var m = mesh m.clearDataChanged() - +proc updateUniforms(renderer: Renderer, scene: var Scene, currentInFlight: int) = + assert scene in renderer.scenedata + var data = renderer.scenedata[scene] + if data.uniformBuffers.len == 0: + return + assert data.uniformBuffers[currentInFlight].vk.valid -proc render*(renderer: var Renderer, scene: Entity) = + for i in 0 ..< renderer.renderPass.subpasses.len: + var subpass = renderer.renderPass.subpasses[i] + for pipeline in subpass.pipelines.mitems: + var offset = 0'u64 + for uniform in pipeline.uniforms: + assert uniform.thetype == scene.shaderGlobals[uniform.name].thetype + let (pdata, size) = scene.shaderGlobals[uniform.name].getRawData() + data.uniformBuffers[currentInFlight].setData(pdata, size, offset) + offset += size + +proc render*(renderer: var Renderer, scene: var Scene) = assert scene in renderer.scenedata var @@ -196,13 +241,14 @@ commandBuffer.beginRenderCommands(renderer.renderPass, renderer.swapchain.currentFramebuffer()) + renderer.updateUniforms(scene, renderer.swapchain.currentInFlight) + for i in 0 ..< renderer.renderPass.subpasses.len: let subpass = renderer.renderPass.subpasses[i] for pipeline in subpass.pipelines: var mpipeline = pipeline commandBuffer.vkCmdBindPipeline(subpass.pipelineBindPoint, mpipeline.vk) commandBuffer.vkCmdBindDescriptorSets(subpass.pipelineBindPoint, mpipeline.layout, 0, 1, addr(mpipeline.descriptorSets[renderer.swapchain.currentInFlight].vk), 0, nil) - mpipeline.updateUniforms(scene, renderer.swapchain.currentInFlight) debug "Scene buffers:" for (location, buffer) in renderer.scenedata[scene].vertexBuffers.pairs: @@ -229,7 +275,6 @@ checkVkResult renderer.device.vk.vkDeviceWaitIdle() oldSwapchain.destroy() - func framesRendered*(renderer: Renderer): uint64 = renderer.swapchain.framesRendered @@ -239,8 +284,13 @@ proc destroy*(renderer: var Renderer) = for data in renderer.scenedata.mvalues: for buffer in data.vertexBuffers.mvalues: + assert buffer.vk.valid buffer.destroy() if data.indexBuffer.vk.valid: + assert data.indexBuffer.vk.valid data.indexBuffer.destroy() + for buffer in data.uniformBuffers.mitems: + assert buffer.vk.valid + buffer.destroy() renderer.renderPass.destroy() renderer.swapchain.destroy() diff -r c774b064def5 -r be6e0f89645a src/semicongine/vulkan/descriptor.nim --- a/src/semicongine/vulkan/descriptor.nim Thu May 04 23:44:15 2023 +0700 +++ b/src/semicongine/vulkan/descriptor.nim Fri May 05 23:45:50 2023 +0700 @@ -1,29 +1,48 @@ import std/enumerate +import std/tables import ./api import ./device import ./buffer import ./utils +import ./image type + DescriptorType* = enum + Uniform, ImageSampler Descriptor* = object # "fields" of a DescriptorSetLayout - thetype*: VkDescriptorType + name*: string count*: uint32 stages*: seq[VkShaderStageFlagBits] itemsize*: uint32 + case thetype*: DescriptorType + of Uniform: + buffer*: Buffer + offset*: uint64 + size*: uint64 + of ImageSampler: + imageview*: ImageView + sampler*: Sampler + DescriptorSet* = object # "instance" of a DescriptorSetLayout + vk*: VkDescriptorSet + layout*: DescriptorSetLayout DescriptorSetLayout* = object # "type-description" of a DescriptorSet device: Device vk*: VkDescriptorSetLayout descriptors*: seq[Descriptor] - DescriptorSet* = object # "instance" of a DescriptorSetLayout - vk*: VkDescriptorSet - layout: DescriptorSetLayout DescriptorPool* = object # required for allocation of DescriptorSet device: Device vk*: VkDescriptorPool maxSets*: uint32 # maximum number of allocatable descriptor sets counts*: seq[(VkDescriptorType, uint32)] # maximum number for each descriptor type to allocate +const DESCRIPTOR_TYPE_MAP = { + Uniform: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + ImageSampler: VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, +}.toTable + +func vkType(descriptor: Descriptor): VkDescriptorType = + DESCRIPTOR_TYPE_MAP[descriptor.thetype] proc createDescriptorSetLayout*(device: Device, descriptors: seq[Descriptor]): DescriptorSetLayout = assert device.vk.valid @@ -35,7 +54,7 @@ for i, descriptor in enumerate(descriptors): layoutbindings.add VkDescriptorSetLayoutBinding( binding: uint32(i), - descriptorType: descriptor.thetype, + descriptorType: descriptor.vkType, descriptorCount: descriptor.count, stageFlags: toBits descriptor.stages, pImmutableSamplers: nil, @@ -105,29 +124,52 @@ for descriptorSet in descriptorSets: result.add DescriptorSet(vk: descriptorSet, layout: layout) -proc setDescriptorSet*(descriptorSet: DescriptorSet, buffer: Buffer, bindingBase=0'u32) = +proc writeDescriptorSet*(descriptorSet: DescriptorSet, bindingBase=0'u32) = # assumes descriptors of the descriptorSet are arranged interleaved in buffer assert descriptorSet.layout.device.vk.valid assert descriptorSet.layout.vk.valid assert descriptorSet.vk.valid - assert buffer.device.vk.valid - assert buffer.vk.valid var descriptorSetWrites: seq[VkWriteDescriptorSet] + var bufferInfos: seq[VkDescriptorBufferInfo] + var imageInfos: seq[VkDescriptorImageInfo] - var offset = VkDeviceSize(0) var i = bindingBase for descriptor in descriptorSet.layout.descriptors: - let length = VkDeviceSize(descriptor.itemsize * descriptor.count) - var bufferInfo = VkDescriptorBufferInfo(buffer: buffer.vk, offset: offset, range: length) - descriptorSetWrites.add VkWriteDescriptorSet( - sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - dstSet: descriptorSet.vk, - dstBinding: i, - dstArrayElement: 0, - descriptorType: descriptor.thetype, - descriptorCount: descriptor.count, - pBufferInfo: addr(bufferInfo), + if descriptor.thetype == Uniform: + assert descriptor.buffer.vk.valid + bufferInfos.add VkDescriptorBufferInfo( + buffer: descriptor.buffer.vk, + offset: descriptor.offset, + range: descriptor.size, ) - offset += length + descriptorSetWrites.add VkWriteDescriptorSet( + sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + dstSet: descriptorSet.vk, + dstBinding: i, + dstArrayElement: 0, + descriptorType: descriptor.vkType, + descriptorCount: descriptor.count, + pBufferInfo: addr bufferInfos[^1], + ) + #[ + elif descriptor.thetype == ImageSampler: + assert descriptor.imageview.vk.valid + assert descriptor.sampler.vk.valid + imageInfos.add VkDescriptorImageInfo( + imageLayout: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + imageView: descriptor.imageview.vk, + sampler: descriptor.sampler.vk, + ) + descriptorSetWrites.add VkWriteDescriptorSet( + sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + dstSet: descriptorSet.vk, + dstBinding: i, + dstArrayElement: 0, + descriptorType: descriptor.vkType, + descriptorCount: descriptor.count, + pImageInfo: addr imageInfos[^1], + ) + ]# + inc i descriptorSet.layout.device.vk.vkUpdateDescriptorSets(uint32(descriptorSetWrites.len), descriptorSetWrites.toCPointer, 0, nil) diff -r c774b064def5 -r be6e0f89645a src/semicongine/vulkan/device.nim --- a/src/semicongine/vulkan/device.nim Thu May 04 23:44:15 2023 +0700 +++ b/src/semicongine/vulkan/device.nim Fri May 05 23:45:50 2023 +0700 @@ -20,7 +20,7 @@ graphics: bool proc `$`*(device: Device): string = - "vk: " & $device.vk & "physicalDevice: " & $device.physicalDevice + "Device: vk=" & $device.vk proc createDevice*( instance: Instance, diff -r c774b064def5 -r be6e0f89645a src/semicongine/vulkan/image.nim --- a/src/semicongine/vulkan/image.nim Thu May 04 23:44:15 2023 +0700 +++ b/src/semicongine/vulkan/image.nim Fri May 05 23:45:50 2023 +0700 @@ -22,9 +22,15 @@ of false: discard of true: memory*: DeviceMemory + Sampler* = object + device*: Device + vk*: VkSampler ImageView* = object vk*: VkImageView image*: Image + Texture* = object + imageView*: ImageView + sampler*: Sampler const DEPTH_FORMAT_MAP = { PixelDepth(1): VK_FORMAT_R8_SRGB, @@ -132,10 +138,15 @@ ) # currently only usable for texture access from shader -proc createImage(device: Device, width, height: uint32, depth: PixelDepth, data: pointer): Image = +proc createImage*(device: Device, width, height: uint32, depth: PixelDepth, data: pointer): Image = assert device.vk.valid + assert width > 0 + assert height > 0 + assert depth != 2 + assert data != nil let size = width * height * depth + result.device = device result.width = width result.height = height result.depth = depth @@ -156,6 +167,7 @@ samples: VK_SAMPLE_COUNT_1_BIT, ) checkVkResult device.vk.vkCreateImage(addr imageInfo, nil, addr result.vk) + result.allocateMemory(requireMappable=false, preferVRAM=true, preferAutoFlush=false) result.transitionImageLayout(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) var stagingBuffer = device.createBuffer(size=size, usage=[VK_BUFFER_USAGE_TRANSFER_SRC_BIT], requireMappable=true, preferVRAM=false, preferAutoFlush=true) @@ -174,7 +186,8 @@ image.memoryAllocated = false image.vk.reset -proc createSampler(device: Device): VkSampler = +proc createSampler*(device: Device): Sampler = + assert device.vk.valid var samplerInfo = VkSamplerCreateInfo( sType: VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, magFilter: VK_FILTER_LINEAR, @@ -193,8 +206,14 @@ minLod: 0, maxLod: 0, ) - checkVkResult device.vk.vkCreateSampler(addr samplerInfo, nil, addr result) + result.device = device + checkVkResult device.vk.vkCreateSampler(addr samplerInfo, nil, addr result.vk) +proc destroy*(sampler: var Sampler) = + assert sampler.device.vk.valid + assert sampler.vk.valid + sampler.device.vk.vkDestroySampler(sampler.vk, nil) + sampler.vk.reset proc createImageView*( image: Image, @@ -234,3 +253,14 @@ assert imageview.vk.valid imageview.image.device.vk.vkDestroyImageView(imageview.vk, nil) imageview.vk.reset() + +proc createTexture*(image: Image): Texture = + assert image.vk.valid + assert image.device.vk.valid + + result.imageView = image.createImageView() + result.sampler = image.device.createSampler() + +proc destroy*(texture: var Texture) = + texture.imageView.destroy() + texture.sampler.destroy() diff -r c774b064def5 -r be6e0f89645a src/semicongine/vulkan/physicaldevice.nim --- a/src/semicongine/vulkan/physicaldevice.nim Thu May 04 23:44:15 2023 +0700 +++ b/src/semicongine/vulkan/physicaldevice.nim Fri May 05 23:45:50 2023 +0700 @@ -20,6 +20,9 @@ index*: uint32 flags*: seq[VkQueueFlagBits] +func `$`*(device: PhysicalDevice): string = + "Physical device: vk=" & $device.vk & ", name=" & $device.name & ", devicetype=" & $device.devicetype + proc getPhysicalDevices*(instance: Instance): seq[PhysicalDevice] = assert instance.vk.valid assert instance.surface.valid diff -r c774b064def5 -r be6e0f89645a src/semicongine/vulkan/pipeline.nim --- a/src/semicongine/vulkan/pipeline.nim Thu May 04 23:44:15 2023 +0700 +++ b/src/semicongine/vulkan/pipeline.nim Fri May 05 23:45:50 2023 +0700 @@ -7,8 +7,8 @@ import ./shader import ./buffer import ./utils +import ./image -import ../entity import ../gpu_data type @@ -20,7 +20,6 @@ descriptorSetLayout*: DescriptorSetLayout descriptorPool*: DescriptorPool descriptorSets*: seq[DescriptorSet] - uniformBuffers: seq[Buffer] func inputs*(pipeline: Pipeline): seq[ShaderAttribute] = for shader in pipeline.shaders: @@ -37,24 +36,23 @@ uniformList[attribute.name] = attribute result = uniformList.values.toSeq -proc setupUniforms(pipeline: var Pipeline, inFlightFrames: int) = +proc setupDescriptors*(pipeline: var Pipeline, buffers: seq[Buffer], textures: seq[Table[string, Texture]], inFlightFrames: int) = assert pipeline.vk.valid - - var uniformBufferSize = 0'u64 - for uniform in pipeline.uniforms: - uniformBufferSize += uniform.thetype.size - if uniformBufferSize == 0: - return + assert buffers.len == 0 or buffers.len == inFlightFrames + # assert textures.len == 0 or textures.len == inFlightFrames for i in 0 ..< inFlightFrames: - var buffer = pipeline.device.createBuffer( - size=uniformBufferSize, - usage=[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT], - requireMappable=true, - preferVRAM=true, - ) - pipeline.uniformBuffers.add buffer - pipeline.descriptorSets[i].setDescriptorSet(buffer) + var offset = 0'u64 + for descriptor in pipeline.descriptorSets[i].layout.descriptors.mitems: + if descriptor.thetype == Uniform: + let size = VkDeviceSize(descriptor.itemsize * descriptor.count) + descriptor.buffer = buffers[i] + descriptor.offset = offset + descriptor.size = size + offset += size + # elif descriptor.thetype == ImageSampler: + # descriptor.imageview = textures[i][descriptor.name].imageView + # descriptor.sampler = textures[i][descriptor.name].sampler proc createPipeline*(device: Device, renderPass: VkRenderPass, vertexCode: ShaderCode, fragmentCode: ShaderCode, inFlightFrames: int, subpass = 0'u32): Pipeline = assert renderPass.valid @@ -67,18 +65,28 @@ assert fragmentShader.stage == VK_SHADER_STAGE_FRAGMENT_BIT assert vertexShader.outputs == fragmentShader.inputs assert vertexShader.uniforms == fragmentShader.uniforms + assert vertexShader.samplers == fragmentShader.samplers result.device = device result.shaders = @[vertexShader, fragmentShader] var descriptors = @[ Descriptor( - thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + name: "Uniforms", + thetype: Uniform, count: 1, stages: @[VK_SHADER_STAGE_VERTEX_BIT, VK_SHADER_STAGE_FRAGMENT_BIT], itemsize: vertexShader.uniforms.size(), ), ] + for sampler in vertexShader.samplers: + descriptors.add Descriptor( + name: sampler.name, + thetype: ImageSampler, + count: 1, + stages: @[VK_SHADER_STAGE_VERTEX_BIT, VK_SHADER_STAGE_FRAGMENT_BIT], + itemsize: 0, + ) result.descriptorSetLayout = device.createDescriptorSetLayout(descriptors) # TODO: Push constants @@ -183,27 +191,13 @@ nil, addr(result.vk) ) - result.descriptorPool = result.device.createDescriptorSetPool(@[(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, uint32(inFlightFrames))]) + var poolsizes = @[(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, uint32(inFlightFrames))] + if vertexShader.samplers.len > 0: + poolsizes.add (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, uint32(inFlightFrames * vertexShader.samplers.len)) + + result.descriptorPool = result.device.createDescriptorSetPool(poolsizes) result.descriptorSets = result.descriptorPool.allocateDescriptorSet(result.descriptorSetLayout, inFlightFrames) discard result.uniforms # just for assertion - result.setupUniforms(inFlightFrames=inFlightFrames) - -proc updateUniforms*(pipeline: Pipeline, rootEntity: Entity, currentInFlight: int) = - if pipeline.uniformBuffers.len == 0: - return - assert pipeline.vk.valid - assert pipeline.uniformBuffers[currentInFlight].vk.valid - - var globalsByName: Table[string, DataValue] - for component in allComponentsOfType[ShaderGlobal](rootEntity): - globalsByName[component.name] = component.value - - var offset = 0'u64 - for uniform in pipeline.uniforms: - assert uniform.thetype == globalsByName[uniform.name].thetype - let (pdata, size) = globalsByName[uniform.name].getRawData() - pipeline.uniformBuffers[currentInFlight].setData(pdata, size, offset) - offset += size proc destroy*(pipeline: var Pipeline) = @@ -212,10 +206,6 @@ assert pipeline.layout.valid assert pipeline.descriptorSetLayout.vk.valid - for buffer in pipeline.uniformBuffers.mitems: - assert buffer.vk.valid - buffer.destroy() - if pipeline.descriptorPool.vk.valid: pipeline.descriptorPool.destroy() diff -r c774b064def5 -r be6e0f89645a src/semicongine/vulkan/shader.nim --- a/src/semicongine/vulkan/shader.nim Thu May 04 23:44:15 2023 +0700 +++ b/src/semicongine/vulkan/shader.nim Fri May 05 23:45:50 2023 +0700 @@ -26,6 +26,7 @@ binary: seq[uint32] inputs*: seq[ShaderAttribute] uniforms*: seq[ShaderAttribute] + samplers*: seq[ShaderAttribute] outputs*: seq[ShaderAttribute] Shader* = object device: Device @@ -34,6 +35,7 @@ entrypoint*: string inputs*: seq[ShaderAttribute] uniforms*: seq[ShaderAttribute] + samplers*: seq[ShaderAttribute] outputs*: seq[ShaderAttribute] @@ -88,6 +90,7 @@ stage: VkShaderStageFlagBits, inputs: seq[ShaderAttribute]= @[], uniforms: seq[ShaderAttribute]= @[], + samplers: seq[ShaderAttribute]= @[], outputs: seq[ShaderAttribute]= @[], version=DEFAULT_SHADER_VERSION , entrypoint=DEFAULT_SHADER_ENTRYPOINT , @@ -97,13 +100,15 @@ var code = @[&"#version {version}", ""] & # var code = @[&"#version {version}", "layout(row_major) uniform;", ""] & (if inputs.len > 0: inputs.glslInput() & @[""] else: @[]) & - (if uniforms.len > 0: uniforms.glslUniforms() & @[""] else: @[]) & + (if uniforms.len > 0: uniforms.glslUniforms(binding=0) & @[""] else: @[]) & + (if samplers.len > 0: samplers.glslSamplers(basebinding=1) & @[""] else: @[]) & (if outputs.len > 0: outputs.glslOutput() & @[""] else: @[]) & @[&"void {entrypoint}(){{"] & main & @[&"}}"] result.inputs = inputs result.uniforms = uniforms + result.samplers = samplers result.outputs = outputs result.entrypoint = entrypoint result.stage = stage @@ -114,12 +119,13 @@ stage: VkShaderStageFlagBits, inputs: seq[ShaderAttribute]= @[], uniforms: seq[ShaderAttribute]= @[], + samplers: seq[ShaderAttribute]= @[], outputs: seq[ShaderAttribute]= @[], version=DEFAULT_SHADER_VERSION , entrypoint=DEFAULT_SHADER_ENTRYPOINT , main: string ): ShaderCode {.compileTime.} = - return compileGlslShader(stage, inputs, uniforms, outputs, version, entrypoint, @[main]) + return compileGlslShader(stage, inputs, uniforms, samplers, outputs, version, entrypoint, @[main]) proc createShaderModule*( @@ -132,6 +138,7 @@ result.device = device result.inputs = shaderCode.inputs result.uniforms = shaderCode.uniforms + result.samplers = shaderCode.samplers result.outputs = shaderCode.outputs result.entrypoint = shaderCode.entrypoint result.stage = shaderCode.stage diff -r c774b064def5 -r be6e0f89645a tests/test_vulkan_wrapper.nim --- a/tests/test_vulkan_wrapper.nim Thu May 04 23:44:15 2023 +0700 +++ b/tests/test_vulkan_wrapper.nim Fri May 05 23:45:50 2023 +0700 @@ -117,11 +117,13 @@ ] vertexOutput = @[attr[Vec3f]("outcolor")] uniforms = @[attr[float32]("time")] + # samplers = @[attr[Sampler2DType]("my_little_texture")] fragOutput = @[attr[Vec4f]("color")] vertexCode = compileGlslShader( stage=VK_SHADER_STAGE_VERTEX_BIT, inputs=vertexInput, uniforms=uniforms, + # samplers=samplers, outputs=vertexOutput, main="""gl_Position = vec4(position + translate, 1.0); outcolor = color;""" ) @@ -129,28 +131,34 @@ stage=VK_SHADER_STAGE_FRAGMENT_BIT, inputs=vertexOutput, uniforms=uniforms, + # samplers=samplers, outputs=fragOutput, - main="color = vec4(outcolor, 0.8);" + # main="color = texture(my_little_texture, outcolor.xy) * 0.5 + vec4(outcolor, 1) * 0.5;" + main="color = vec4(outcolor, 1) * 0.5;" ) var renderPass = engine.gpuDevice.simpleForwardRenderPass(vertexCode, fragmentCode) engine.setRenderer(renderPass) # INIT SCENES - var scenes = [scene_simple(), scene_different_mesh_types(), scene_primitives()] - var time = initShaderGlobal("time", 0.0'f32) + var scenes = [ + newScene("simple", scene_simple()), + newScene("different mesh types", scene_different_mesh_types()), + newScene("primitives", scene_primitives()) + ] for scene in scenes.mitems: - scene.components.add time + scene.addShaderGlobal("time", 0.0'f32) engine.addScene(scene, vertexInput) # MAINLOOP echo "Setup successfull, start rendering" for i in 0 ..< 3: - for scene in scenes: + for scene in scenes.mitems: for i in 0 ..< 1000: if engine.updateInputs() != Running or engine.keyIsDown(Escape): engine.destroy() return - setValue[float32](time.value, get[float32](time.value) + 0.0005) + var time = scene.getShaderGlobal("time") + setValue[float32](time, get[float32](time) + 0.0005) engine.renderScene(scene) echo "Rendered ", engine.framesRendered, " frames" echo "Processed ", engine.eventsProcessed, " events"