# HG changeset patch # User Sam # Date 1685169842 -25200 # Node ID d1ee2a815fa1572713c19ad5c64d4ca1ea3825a3 # Parent f4079f409638c66c947a9b890d045803a157f29f add: some api improvments, preparing for font-loading diff -r f4079f409638 -r d1ee2a815fa1 src/semicongine.nim --- a/src/semicongine.nim Fri May 26 00:49:58 2023 +0700 +++ b/src/semicongine.nim Sat May 27 13:44:02 2023 +0700 @@ -9,6 +9,7 @@ import semicongine/renderer import semicongine/resources import semicongine/settings +import semicongine/text import semicongine/platform/window import semicongine/vulkan @@ -20,5 +21,6 @@ export renderer export resources export settings +export text export window export vulkan diff -r f4079f409638 -r d1ee2a815fa1 src/semicongine/core/fonttypes.nim --- a/src/semicongine/core/fonttypes.nim Fri May 26 00:49:58 2023 +0700 +++ b/src/semicongine/core/fonttypes.nim Sat May 27 13:44:02 2023 +0700 @@ -2,7 +2,11 @@ import std/unicode import ./imagetypes +import ./vector type Font* = object - bitmaps*: Table[Rune, Image] + name*: string # used to reference fontAtlas will be referenced in shader + characterUVs*: Table[Rune, array[4, Vec2f]] + characterDimensions*: Table[Rune, Vec2f] + fontAtlas*: Image diff -r f4079f409638 -r d1ee2a815fa1 src/semicongine/core/imagetypes.nim --- a/src/semicongine/core/imagetypes.nim Fri May 26 00:49:58 2023 +0700 +++ b/src/semicongine/core/imagetypes.nim Sat May 27 13:44:02 2023 +0700 @@ -14,10 +14,9 @@ filter*: VkFilter # TODO: replace with mag/minification Image* = ref ImageObject - TextureObject = object + Texture* = object image*: Image sampler*: Sampler - Texture* = ref TextureObject proc DefaultSampler*(): Sampler = Sampler( @@ -29,7 +28,7 @@ proc newImage*(width, height: uint32, imagedata: seq[Pixel] = @[]): Image = assert width > 0 and height > 0 - assert uint32(imagedata.len) == width * height + assert uint32(imagedata.len) == width * height or imagedata.len == 0 result = new Image result.imagedata = (if imagedata.len == 0: newSeq[Pixel](width * height) else: imagedata) @@ -37,3 +36,16 @@ result.width = width result.height = height + +proc `[]`*(image: Image, x, y: uint32): Pixel = + assert x < image.width + assert y < image.height + + image[].imagedata[y * image.width + x] + +proc `[]=`*(image: var Image, x, y: uint32, value: Pixel) = + assert x < image.width + assert y < image.height + + image[].imagedata[y * image.width + x] = value + diff -r f4079f409638 -r d1ee2a815fa1 src/semicongine/renderer.nim --- a/src/semicongine/renderer.nim Fri May 26 00:49:58 2023 +0700 +++ b/src/semicongine/renderer.nim Sat May 27 13:44:02 2023 +0700 @@ -17,6 +17,7 @@ import ./scene import ./mesh +import ./text type SceneData = object @@ -163,7 +164,23 @@ indexBufferOffset += size data.drawables[mesh] = drawable - # setup uniforms and textures + # extract textures + var sampler = DefaultSampler() + sampler.magnification = VK_FILTER_NEAREST + sampler.minification = VK_FILTER_NEAREST + # for mesh in allComponentsOfType[Mesh](scene.root): + for textbox in allEntitiesOfType[Textbox](scene.root): + if not (textbox.font.name in data.textures): + data.textures[textbox.font.name] = @[ + renderer.device.uploadTexture(Texture(image: textbox.font.fontAtlas, sampler: sampler)) + ] + + for name, textures in scene.textures.pairs: + data.textures[name] = @[] + for texture in textures: + data.textures[name].add renderer.device.uploadTexture(texture) + + # setup uniforms and samplers for subpass_i in 0 ..< renderer.renderPass.subpasses.len: for pipeline in renderer.renderPass.subpasses[subpass_i].pipelines.mitems: var uniformBufferSize = 0'u64 @@ -177,10 +194,6 @@ requireMappable=true, preferVRAM=true, ) - for name, textures in scene.textures.pairs: - data.textures[name] = @[] - for texture in textures: - data.textures[name].add renderer.device.uploadTexture(texture) var poolsizes = @[(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, uint32(renderer.swapchain.inFlightFrames))] if samplers.len > 0: diff -r f4079f409638 -r d1ee2a815fa1 src/semicongine/resources.nim --- a/src/semicongine/resources.nim Fri May 26 00:49:58 2023 +0700 +++ b/src/semicongine/resources.nim Sat May 27 13:44:02 2023 +0700 @@ -2,6 +2,7 @@ import std/strutils import std/strformat import std/os +import std/unicode import ./core import ./resources/image @@ -126,8 +127,9 @@ else: raise newException(Exception, "Unsupported audio file type: " & path) -proc loadFont*(path: string): Font = - loadResource_intern(path).readTrueType() +proc loadFont*(path: string, name: string): Font = + let defaultCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=+[{]};:,<.>/?".toRunes() + loadResource_intern(path).readTrueType(name, defaultCharset) proc loadMesh*(path: string): Entity = loadResource_intern(path).readglTF()[0].root diff -r f4079f409638 -r d1ee2a815fa1 src/semicongine/resources/font.nim --- a/src/semicongine/resources/font.nim Fri May 26 00:49:58 2023 +0700 +++ b/src/semicongine/resources/font.nim Sat May 27 13:44:02 2023 +0700 @@ -1,8 +1,10 @@ import std/strformat +import std/tables import std/streams import std/os import std/unicode +import ../core/vector import ../core/imagetypes import ../core/fonttypes @@ -18,24 +20,53 @@ proc stbtt_GetCodepointBitmap(info: ptr stbtt_fontinfo, scale_x: cfloat, scale_y: cfloat, codepoint: cint, width: ptr cint, height: ptr cint, xoff: ptr cint, yoff: ptr cint): cstring {.importc, nodecl.} # proc free(p: pointer) {.importc.} -proc readTrueType*(stream: Stream): Font = +proc readTrueType*(stream: Stream, name: string, codePoints: seq[Rune]): Font = var indata = stream.readAll() fontinfo: stbtt_fontinfo if stbtt_InitFont(addr fontinfo, addr indata[0], 0) == 0: raise newException(Exception, "An error occured while loading PNG file") + + let fontheight = stbtt_ScaleForPixelHeight(addr fontinfo, 100) var - width, height: cint - offsetX, offsetY: cint - data = stbtt_GetCodepointBitmap(addr fontinfo, 0, stbtt_ScaleForPixelHeight(addr fontinfo, 20), cint('a'), addr width, addr height, addr offsetX, addr offsetY) - echo width, "x", height - echo "offset: ", offsetX, "x", offsetY - for y in 0 ..< height: - for x in 0 ..< width: - if data[y * width + x] > char(128): - write stdout, '#' - else: - write stdout, ' ' - write stdout, ' ' - write stdout, '\n' - result + charOffset: Table[Rune, uint32] + offsetX: uint32 + maxheight: uint32 + bitmaps: Table[Rune, (cstring, cint, cint)] + baselines: Table[Rune, int] + for codePoint in codePoints: + var + width, height: cint + leftStart, baseline: cint + data = stbtt_GetCodepointBitmap( + addr fontinfo, + 0, fontheight, + cint('a'), + addr width, addr height, + addr leftStart, addr baseline + ) + bitmaps[codePoint] = (data, width, height) + maxheight = max(maxheight, uint32(height)) + charOffset[codePoint] = offsetX + offsetX += uint32(width) + baselines[codePoint] = baseline + + result.name = name + result.fontAtlas = newImage(offsetX, maxheight) + + offsetX = 0 + for codePoint in codePoints: + let d = bitmaps[codePoint][0] + let width = uint32(bitmaps[codePoint][1]) + let height = uint32(bitmaps[codePoint][2]) + for y in 0 ..< height: + for x in 0 ..< width: + result.fontAtlas[x + offsetX, y] = [255'u8, 255'u8, 255'u8, uint8(d[y * width + x])] + result.characterDimensions[codePoint] = newVec2f(float32(width), float32(height)) + result.characterUVs[codePoint] = [ + newVec2f(float32(offsetX) / float32(result.fontAtlas.width), 0), + newVec2f(float32(offsetX + width) / float32(result.fontAtlas.width), 0), + newVec2f(float32(offsetX) / float32(result.fontAtlas.width), 1), + newVec2f(float32(offsetX + width) / float32(result.fontAtlas.width), 1), + ] + offsetX += width diff -r f4079f409638 -r d1ee2a815fa1 src/semicongine/resources/mesh.nim --- a/src/semicongine/resources/mesh.nim Fri May 26 00:49:58 2023 +0700 +++ b/src/semicongine/resources/mesh.nim Sat May 27 13:44:02 2023 +0700 @@ -1,4 +1,5 @@ import std/strutils +import std/options import std/json import std/logging import std/tables @@ -12,7 +13,6 @@ import ./image - type glTFHeader = object magic: uint32 @@ -23,17 +23,17 @@ binaryBufferData: seq[uint8] glTFMaterial = object color: Vec4f - colorTexture: Texture + colorTexture: Option[Texture] colorTextureIndex: uint32 metallic: float32 roughness: float32 - metallicRoughnessTexture: Texture + metallicRoughnessTexture: Option[Texture] metallicRoughnessTextureIndex: uint32 - normalTexture: Texture + normalTexture: Option[Texture] normalTextureIndex: uint32 - occlusionTexture: Texture + occlusionTexture: Option[Texture] occlusionTextureIndex: uint32 - emissiveTexture: Texture + emissiveTexture: Option[Texture] emissiveTextureIndex: uint32 emissiveFactor: Vec3f @@ -272,7 +272,6 @@ 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() @@ -298,10 +297,10 @@ 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.colorTexture = some(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.metallicRoughnessTexture = some(loadTexture(root, pbr["metallicRoughnessTexture"]["index"].getInt(), mainBuffer)) result.metallicRoughnessTextureIndex = pbr["metallicRoughnessTexture"].getOrDefault("texCoord").getInt().uint32 if pbr.hasKey("metallicFactor"): result.metallic = pbr["metallicFactor"].getFloat() @@ -309,13 +308,13 @@ result.roughness= pbr["roughnessFactor"].getFloat() if materialNode.hasKey("normalTexture"): - result.normalTexture = loadTexture(root, materialNode["normalTexture"]["index"].getInt(), mainBuffer) + result.normalTexture = some(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.occlusionTexture = some(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.emissiveTexture = some(loadTexture(root, materialNode["emissiveTexture"]["index"].getInt(), mainBuffer)) result.occlusionTextureIndex = materialNode["emissiveTexture"].getOrDefault("texCoord").getInt().uint32 if materialNode.hasKey("roughnessFactor"): result.roughness = materialNode["roughnessFactor"].getFloat() @@ -372,22 +371,22 @@ 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 + if not m.colorTexture.isSome: + colorTexture.add m.colorTexture.get colorTextureIndex.add m.colorTextureIndex metallic.add m.metallic roughness.add m.roughness - if not m.metallicRoughnessTexture.isNil: - metallicRoughnessTexture.add m.metallicRoughnessTexture + if not m.metallicRoughnessTexture.isSome: + metallicRoughnessTexture.add m.metallicRoughnessTexture.get metallicRoughnessTextureIndex.add m.metallicRoughnessTextureIndex - if not m.normalTexture.isNil: - normalTexture.add m.normalTexture + if not m.normalTexture.isSome: + normalTexture.add m.normalTexture.get normalTextureIndex.add m.normalTextureIndex - if not m.occlusionTexture.isNil: - occlusionTexture.add m.occlusionTexture + if not m.occlusionTexture.isSome: + occlusionTexture.add m.occlusionTexture.get occlusionTextureIndex.add m.occlusionTextureIndex - if not m.emissiveTexture.isNil: - emissiveTexture.add m.emissiveTexture + if not m.emissiveTexture.isSome: + emissiveTexture.add m.emissiveTexture.get emissiveTextureIndex.add m.emissiveTextureIndex emissiveFactor.add m.emissiveFactor diff -r f4079f409638 -r d1ee2a815fa1 src/semicongine/scene.nim --- a/src/semicongine/scene.nim Fri May 26 00:49:58 2023 +0700 +++ b/src/semicongine/scene.nim Sat May 27 13:44:02 2023 +0700 @@ -135,6 +135,15 @@ result.name = &"Entity[{$(cast[ByteAddress](result))}]" result.transform = Unit4 +iterator allEntitiesOfType*[T: Entity](root: Entity): T = + var queue = @[root] + while queue.len > 0: + let entity = queue.pop + if entity of T: + yield T(entity) + for i in countdown(entity.children.len - 1, 0): + queue.add entity.children[i] + iterator allComponentsOfType*[T: Component](root: Entity): var T = var queue = @[root] while queue.len > 0: diff -r f4079f409638 -r d1ee2a815fa1 src/semicongine/text.nim --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/semicongine/text.nim Sat May 27 13:44:02 2023 +0700 @@ -0,0 +1,35 @@ +import ./scene +import ./mesh +import ./core/vector +import ./core/matrix +import ./core/fonttypes + +type + TextAlignment = enum + Left + Center + Right + Textbox* = ref object of Entity + columns*: uint32 + rows*: uint32 + text*: string + alignment*: TextAlignment + font*: Font + lettermesh*: Mesh + +func len*(textbox: Textbox): uint32 = + textbox.columns * textbox.rows + +proc newTextbox*(columns, rows: uint32, font: Font, text=""): Textbox = + result = Textbox(columns: columns, rows: rows, text: text, font: font) + result.lettermesh = newMesh( + positions = [newVec3f(0, 0), newVec3f(0, 1), newVec3f(1, 1), newVec3f(1, 0)], + indices = [[0'u16, 1'u16, 2'u16], [0'u16, 0'u16, 0'u16]], + uvs = [newVec2f(0, 0), newVec2f(0, 1), newVec2f(1, 1), newVec2f(1, 0)], + instanceCount = result.len, + ) + var transforms = newSeq[Mat4](result.len) + for i in 0 ..< result.len: + transforms[i] = Unit4f32 + setInstanceData(result.lettermesh, "transform", transforms) + result.components.add result.lettermesh