Repository: hYdos/Blaze4D Branch: master Commit: 4b3bb5a815f6 Files: 170 Total size: 978.1 KB Directory structure: gitextract_4ufyiq5t/ ├── .github/ │ └── workflows/ │ ├── build.yml │ ├── publish.yml │ └── test.yml ├── .gitignore ├── README.md ├── buildSrc/ │ ├── .gitignore │ ├── build.gradle.kts │ └── src/ │ └── main/ │ └── kotlin/ │ └── graphics/ │ └── kiln/ │ └── blaze4d/ │ └── build/ │ └── assets/ │ ├── AssetsPlugin.kt │ ├── AssetsPluginExtension.kt │ └── shaders/ │ ├── CompileShadersTask.kt │ ├── CompilerConfig.kt │ ├── ShaderCompiler.kt │ ├── ShaderModule.kt │ ├── ShaderProject.kt │ ├── ShaderStage.kt │ └── SprivVersion.kt ├── core/ │ ├── LICENSE │ ├── api/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── build.gradle.kts │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ ├── graphics/ │ │ │ └── kiln/ │ │ │ └── blaze4d/ │ │ │ └── core/ │ │ │ ├── Blaze4DCore.java │ │ │ ├── Frame.java │ │ │ ├── GlobalImage.java │ │ │ ├── GlobalMesh.java │ │ │ ├── natives/ │ │ │ │ ├── ImageDataNative.java │ │ │ │ ├── Lib.java │ │ │ │ ├── McUniformDataNative.java │ │ │ │ ├── MeshDataNative.java │ │ │ │ ├── Natives.java │ │ │ │ ├── PipelineConfigurationNative.java │ │ │ │ ├── Vec2u32Native.java │ │ │ │ └── VertexFormatNative.java │ │ │ └── types/ │ │ │ ├── B4DFormat.java │ │ │ ├── B4DImageData.java │ │ │ ├── B4DIndexType.java │ │ │ ├── B4DMeshData.java │ │ │ ├── B4DPrimitiveTopology.java │ │ │ ├── B4DUniform.java │ │ │ ├── B4DUniformData.java │ │ │ ├── B4DVertexFormat.java │ │ │ ├── BlendFactor.java │ │ │ ├── BlendOp.java │ │ │ ├── CompareOp.java │ │ │ └── PipelineConfiguration.java │ │ └── module-info.java │ ├── assets/ │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src/ │ │ ├── debug/ │ │ │ ├── apply.frag │ │ │ ├── apply.vert │ │ │ ├── basic.frag │ │ │ ├── basic.vert │ │ │ └── font/ │ │ │ ├── JetBrainsMono/ │ │ │ │ ├── OFL.txt │ │ │ │ └── tmp_built/ │ │ │ │ └── regular.json │ │ │ ├── charset.txt │ │ │ ├── msdf_font.frag │ │ │ └── msdf_font.vert │ │ ├── emulator/ │ │ │ ├── debug/ │ │ │ │ ├── background.frag │ │ │ │ ├── background.vert │ │ │ │ ├── color.vert │ │ │ │ ├── debug.frag │ │ │ │ ├── null.vert │ │ │ │ ├── position.vert │ │ │ │ ├── textured.frag │ │ │ │ └── uv.vert │ │ │ └── mc_uniforms.glsl │ │ └── utils/ │ │ ├── blit.frag │ │ └── full_screen_quad.vert │ └── natives/ │ ├── .cargo/ │ │ └── config.toml │ ├── .gitignore │ ├── Cargo.toml │ ├── build.gradle.kts │ ├── build.rs │ ├── examples/ │ │ └── immediate_cube.rs │ ├── libvma/ │ │ └── CMakeLists.txt │ ├── rustfmt.toml │ ├── src/ │ │ ├── allocator/ │ │ │ ├── mod.rs │ │ │ └── vma.rs │ │ ├── b4d.rs │ │ ├── c_api.rs │ │ ├── c_log.rs │ │ ├── device/ │ │ │ ├── device.rs │ │ │ ├── device_utils.rs │ │ │ ├── init.rs │ │ │ ├── mod.rs │ │ │ └── surface.rs │ │ ├── glfw_surface.rs │ │ ├── instance/ │ │ │ ├── debug_messenger.rs │ │ │ ├── init.rs │ │ │ ├── instance.rs │ │ │ └── mod.rs │ │ ├── lib.rs │ │ ├── objects/ │ │ │ ├── id.rs │ │ │ ├── mod.rs │ │ │ ├── object_set.rs │ │ │ └── sync.rs │ │ ├── renderer/ │ │ │ ├── emulator/ │ │ │ │ ├── debug_pipeline.rs │ │ │ │ ├── descriptors.rs │ │ │ │ ├── global_objects.rs │ │ │ │ ├── immediate.rs │ │ │ │ ├── mc_shaders.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── pass.rs │ │ │ │ ├── pipeline.rs │ │ │ │ ├── share.rs │ │ │ │ ├── staging.rs │ │ │ │ └── worker.rs │ │ │ └── mod.rs │ │ ├── util/ │ │ │ ├── alloc.rs │ │ │ ├── format.rs │ │ │ ├── id.rs │ │ │ ├── mod.rs │ │ │ ├── rand.rs │ │ │ ├── slice_splitter.rs │ │ │ └── vk.rs │ │ ├── vk/ │ │ │ ├── mod.rs │ │ │ ├── objects/ │ │ │ │ ├── buffer.rs │ │ │ │ ├── image.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── surface.rs │ │ │ │ ├── swapchain.rs │ │ │ │ └── types.rs │ │ │ └── test.rs │ │ └── window.rs │ └── tests/ │ └── test_common/ │ └── mod.rs ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── mod/ │ ├── .gitignore │ ├── LICENSE │ ├── build.gradle.kts │ ├── gradle.properties │ └── src/ │ └── main/ │ ├── java/ │ │ └── graphics/ │ │ └── kiln/ │ │ └── blaze4d/ │ │ ├── Blaze4D.java │ │ ├── Blaze4DMixinPlugin.java │ │ ├── Blaze4DPreLaunch.java │ │ ├── api/ │ │ │ ├── B4DShader.java │ │ │ ├── B4DUniform.java │ │ │ ├── B4DVertexBuffer.java │ │ │ └── Utils.java │ │ ├── emulation/ │ │ │ └── GLStateTracker.java │ │ └── mixin/ │ │ ├── integration/ │ │ │ ├── FramebufferMixin.java │ │ │ ├── GLXMixin.java │ │ │ ├── GlDebugMixin.java │ │ │ ├── GlStateManagerMixin.java │ │ │ ├── MinecraftClientMixin.java │ │ │ ├── VertexFormatMixin.java │ │ │ ├── VideoWarningManagerMixin.java │ │ │ ├── WindowFramebufferMixin.java │ │ │ └── WindowMixin.java │ │ ├── render/ │ │ │ ├── BufferUploaderMixin.java │ │ │ ├── RenderSystemMixin.java │ │ │ ├── VertexBufferMixin.java │ │ │ └── WorldRendererMixin.java │ │ ├── shader/ │ │ │ ├── GlStateManagerMixin.java │ │ │ ├── GlUniformMixin.java │ │ │ ├── RenderSystemMixin.java │ │ │ ├── ShaderAccessor.java │ │ │ └── ShaderMixin.java │ │ └── texture/ │ │ ├── LightmapTextureManagerMixin.java │ │ ├── NativeImageMixin.java │ │ ├── RenderSystemMixin.java │ │ ├── TextureManagerMixin.java │ │ ├── TextureUtilMixin_Development.java │ │ └── TextureUtilMixin_Runtime.java │ └── resources/ │ ├── blaze4d.aw │ ├── blaze4d.mixins.json │ └── fabric.mod.json └── settings.gradle.kts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/build.yml ================================================ name: Build on: - pull_request - push jobs: build: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" strategy: matrix: java: - 18 os: - ubuntu-20.04 - windows-latest runs-on: ${{ matrix.os }} steps: - name: Checkout repository uses: actions/checkout@v2 with: submodules: recursive - name: Validate gradle wrapper uses: gradle/wrapper-validation-action@v1 - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@v1 with: java-version: ${{ matrix.java }} - name: Build run: ./gradlew build - name: Upload artifacts uses: actions/upload-artifact@v2 with: name: Blaze4D-${{ matrix.java }}-${{ matrix.os }} path: build/libs/ ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish to hydos Maven on: push: branches: - master jobs: publish: if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v2 - name: Validate gradle wrapper uses: gradle/wrapper-validation-action@v1 - name: Set up JDK 18 uses: actions/setup-java@v1 with: java-version: 18 - name: Publish run: ./gradlew publishAllPublicationsToHydosRepository env: MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} ================================================ FILE: .github/workflows/test.yml ================================================ on: [ push, pull_request ] name: Build jobs: build: strategy: matrix: os: [ubuntu-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - name: Checkout sources uses: actions/checkout@v2 - name: Prepare Vulkan SDK uses: humbletim/install-vulkan-sdk@v1.1.1 with: version: latest cache: true - name: Setup Rust uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - name: Setup Java uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: '17' - name: Compile resources run: ./gradlew working-directory: resources - name: Build uses: actions-rs/cargo@v1 with: command: build args: --release - name: Upload binaries uses: actions/upload-artifact@v3 with: name: bin-${{ matrix.os }} path: | target/release/*b4d_core.* ================================================ FILE: .gitignore ================================================ # gradle .gradle/ # eclipse *.launch # idea .idea/ *.iml *.ipr *.iws # vscode .settings/ .vscode/ bin/ .classpath .project # macos *.DS_Store # random log files *.log # renderdoc captures *.cap ================================================ FILE: README.md ================================================ ![blaze](https://user-images.githubusercontent.com/68126718/125143247-71be4580-e0f0-11eb-88bc-070eb2838435.png) ## Information Blaze4D is a Fabric mod that changes Minecraft's rendering engine to use the Vulkan Graphics Library, it is currently in Early Development and is **NOT** intended for use by the faint-hearted. Support for Blaze4D can be found in the #support Discord channel. ## Community We have a [Discord server](https://discord.gg/H93wJePuWf) where you can track development progress, ask questions, or just hang out in. ## Building ### Build Requirements - Vulkan SDK - Version 1.3.208 or newer - CMake and a C++ compiler - Required for building some of our dependencies - Rust - Version 1.62.0 or newer - Java 18 ### Build Steps To build the project with natives for your platform run ``` ./gradlew build ``` in the project root directory. To run the game with the mod use any of the 3 run targets: - `./gradlew runClient` - `./gradlew runClientWithValidation` - Enables validation layers - `./gradlew runClientWithValidationRenderdoc` - Enables validation layers and automatically loads the renderdoc shared library. #### Manually building natives To work on and test natives it can be useful to run cargo manually. To do this it's necessary to first build the assets by running ``` ./gradlew :core:assets:build ``` This only needs to be repeated if the assets are modified. After that the natives can be manually built using cargo. ## Contributing 1. Clone the repository (https://github.com/Blaze4D-MC/Blaze4D.git). 2. Edit 3. Pull Request ## Project Structure The project is organized in 2 parts ### Core This is the core of Blaze4D that performs the actual rendering and is written in Rust. The gradle project contains 3 subprojects. - assets - These are any assets we need to bundle with Blaze4D. For example shaders or fonts. They currently need to be separately built after a change using their gradle `build` task. - natives - The main Blaze4D core rust code. - api - A java api of the rust code used by the mod. ### Mod This is the fabric mod itself. Its job is to interface with minecraft. Most of the heavy lifting should take place in Blaze4D core. ================================================ FILE: buildSrc/.gitignore ================================================ # Gradle local files /build/ ================================================ FILE: buildSrc/build.gradle.kts ================================================ plugins { kotlin("jvm") version "1.7.10" } repositories { mavenCentral() } dependencies { implementation(gradleApi()) } ================================================ FILE: buildSrc/src/main/kotlin/graphics/kiln/blaze4d/build/assets/AssetsPlugin.kt ================================================ package graphics.kiln.blaze4d.build.assets import org.gradle.api.Action import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.ExtensionAware import org.gradle.api.tasks.Delete import java.io.File import graphics.kiln.blaze4d.build.assets.shaders.CompileShadersTask; class AssetsPlugin : Plugin { public lateinit var extension: AssetsPluginExtension; public lateinit var outputDir: File; override fun apply(project: Project) { extension = project.extensions.create("assets", AssetsPluginExtension::class.java); outputDir = File(project.buildDir, "out"); project.tasks.register("compileShaders", CompileShadersTask::class.java); project.tasks.create("build", { it.dependsOn("compileShaders") }); project.afterEvaluate({ project.tasks.getByName("compileShaders", { it.inputs.dir(extension.getShaderCompiler().generateSourceDir(project)); }) }) } } ================================================ FILE: buildSrc/src/main/kotlin/graphics/kiln/blaze4d/build/assets/AssetsPluginExtension.kt ================================================ package graphics.kiln.blaze4d.build.assets import graphics.kiln.blaze4d.build.assets.shaders.ShaderCompiler import org.gradle.api.Action import org.gradle.api.tasks.Nested abstract class AssetsPluginExtension { @Nested public abstract fun getShaderCompiler(): ShaderCompiler; fun shaders(configure: Action) { configure.execute(this.getShaderCompiler()); } } ================================================ FILE: buildSrc/src/main/kotlin/graphics/kiln/blaze4d/build/assets/shaders/CompileShadersTask.kt ================================================ package graphics.kiln.blaze4d.build.assets.shaders import graphics.kiln.blaze4d.build.assets.AssetsPlugin import graphics.kiln.blaze4d.build.assets.AssetsPluginExtension import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction import java.io.File import javax.inject.Inject abstract class CompileShadersTask : DefaultTask() { init { outputs.dir(project.buildDir) } @TaskAction fun compile() { var assetsPlugin = project.plugins.getPlugin(AssetsPlugin::class.java); project.delete(assetsPlugin.outputDir); assetsPlugin.extension.getShaderCompiler().compile(project, assetsPlugin.outputDir); } } ================================================ FILE: buildSrc/src/main/kotlin/graphics/kiln/blaze4d/build/assets/shaders/CompilerConfig.kt ================================================ package graphics.kiln.blaze4d.build.assets.shaders import java.io.File data class CompilerConfig( val targetSpriv: SprivVersion, val includeDirs: Set, ) ================================================ FILE: buildSrc/src/main/kotlin/graphics/kiln/blaze4d/build/assets/shaders/ShaderCompiler.kt ================================================ package graphics.kiln.blaze4d.build.assets.shaders import org.gradle.api.Action import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Project import org.gradle.api.file.RelativePath import org.gradle.api.provider.Property import org.gradle.api.tasks.Input import java.io.File abstract class ShaderCompiler { @get:Input public abstract val targetSpirv: Property; @get:Input public abstract val sourceDir: Property; @get:Input public abstract val outputDir: Property; @get:Input public abstract val projects: NamedDomainObjectContainer; public fun targetSpriv(version: SprivVersion) { this.targetSpirv.set(version) } public fun sourceDir(path: String) { this.sourceDir.set(RelativePath.parse(false, path)) } public fun outputDir(path: String) { this.outputDir.set(RelativePath.parse(false, path)) } public fun addProject(name: String, configure: Action) { this.projects.create(name, configure) } fun generateSourceDir(project: Project): File { return this.sourceDir.getOrElse(RelativePath.parse(false, "src")).getFile(project.projectDir); } fun compile(project: Project, outBaseDir: File) { var srcBaseDir = this.generateSourceDir(project); var newOutBaseDir = this.outputDir.getOrElse(RelativePath.parse(false, "")).getFile(outBaseDir); var targetSpirv = this.targetSpirv.getOrElse(SprivVersion.SPV_1_0); var config = CompilerConfig(targetSpirv, HashSet()); this.projects.forEach({ it.compile(project, srcBaseDir, newOutBaseDir, config) }) } } ================================================ FILE: buildSrc/src/main/kotlin/graphics/kiln/blaze4d/build/assets/shaders/ShaderModule.kt ================================================ package graphics.kiln.blaze4d.build.assets.shaders import org.gradle.api.file.RelativePath import org.gradle.api.provider.Property import org.gradle.api.tasks.Input import java.io.File abstract class ShaderModule { public abstract fun getName(): String; @get:Input public abstract val srcFile: Property; @get:Input public abstract val outFile: Property; public fun srcFile(file: String) { this.srcFile.set(RelativePath.parse(true, file)) } fun generateArg(srcBaseDir: File, outBaseDir: File): Array { var relativeSrcFile = this.srcFile.getOrElse(RelativePath.parse(true, this.getName())); var srcFile = relativeSrcFile.getFile(srcBaseDir); var generatedOutFile = "${srcFile.nameWithoutExtension}_${srcFile.extension}.spv"; var outFile = this.outFile.getOrElse(relativeSrcFile.replaceLastName(generatedOutFile)).getFile(outBaseDir); var outDir = outFile.parentFile; if(!outDir.exists()) { if (!outDir.mkdirs()) { throw java.lang.RuntimeException("Failed to create output directory for shader module. ${outDir}"); } } return arrayOf("${srcFile.absolutePath}", "-o${outFile.absolutePath}"); } } ================================================ FILE: buildSrc/src/main/kotlin/graphics/kiln/blaze4d/build/assets/shaders/ShaderProject.kt ================================================ package graphics.kiln.blaze4d.build.assets.shaders import org.gradle.api.Action import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Project import org.gradle.api.file.RelativePath import org.gradle.api.provider.Property import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.Input import java.io.File abstract class ShaderProject { public abstract fun getName(): String; @get:Input public abstract val projectDir: Property; @get:Input public abstract val targetSpriv: Property; @get:Input public abstract val includeDirs: SetProperty; @get:Input public abstract val modules: NamedDomainObjectContainer public fun projectDir(path: String) { this.projectDir.set(RelativePath.parse(false, path)) } public fun targetSpriv(version: SprivVersion) { this.targetSpriv.set(version) } public fun addIncludeDir(path: String) { this.includeDirs.add(RelativePath.parse(false, path)) } public fun addModule(name: String) { this.modules.create(name) } public fun addModule(name: String, configure: Action) { this.modules.create(name, configure) } fun compile(project: Project, srcBaseDir: File, outBaseDir: File, parentConfig: CompilerConfig) { val projectDir = this.projectDir.getOrElse(RelativePath.parse(false, "")); val newSrcBaseDir = projectDir.getFile(srcBaseDir); val newOutBaseDir = projectDir.getFile(outBaseDir); val targetSpriv = this.targetSpriv.getOrElse(parentConfig.targetSpriv); val includeDirs = HashSet(parentConfig.includeDirs); this.includeDirs.orNull?.forEach({ includeDirs.add(it.getFile(newSrcBaseDir)); }); includeDirs.add(newSrcBaseDir); val config = CompilerConfig(targetSpriv, includeDirs); this.execCompile(project, newSrcBaseDir, newOutBaseDir, config); } private fun execCompile(project: Project, srcBaseDir: File, outBaseDir: File, config: CompilerConfig) { this.modules.forEach({ var moduleArgs = it.generateArg(srcBaseDir, outBaseDir); project.exec({ it.executable("glslc") it.args(config.targetSpriv.cliArg) var exec = it; config.includeDirs.forEach({ exec.args("-I${it}") }) moduleArgs.forEach({ exec.args(it) }) }) }) } } ================================================ FILE: buildSrc/src/main/kotlin/graphics/kiln/blaze4d/build/assets/shaders/ShaderStage.kt ================================================ package graphics.kiln.blaze4d.build.assets.shaders enum class ShaderStage(cliArg: String) { VERTEX("-fshader-stage=vertex"), FRAGMENT("-fshader-stage=fragment"), TESSELATION_CONTROL("-fshader-stage=tesscontrol"), TESSELATION_EVALUATION("-fshader-stage=tesseval"), GEOMETRY("-fshader-stage=geometry"), COMPUTE("-fshader-stage=compute"), } ================================================ FILE: buildSrc/src/main/kotlin/graphics/kiln/blaze4d/build/assets/shaders/SprivVersion.kt ================================================ package graphics.kiln.blaze4d.build.assets.shaders enum class SprivVersion(val cliArg: String) { SPV_1_0("--target-spv=spv1.0"), SPV_1_1("--target-spv=spv1.1"), SPV_1_2("--target-spv=spv1.2"), SPV_1_3("--target-spv=spv1.3"), SPV_1_4("--target-spv=spv1.4"), SPV_1_5("--target-spv=spv1.5"), SPV_1_6("--target-spv=spv1.6"), } ================================================ FILE: core/LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: core/api/.gitignore ================================================ # Gradle local files /build/ ================================================ FILE: core/api/LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: core/api/build.gradle.kts ================================================ plugins { id("java") id("fr.stardustenterprises.rust.importer") version "3.2.4" } group = "graphics.kiln" version = "1.0.0-SNAPSHOT" repositories { mavenCentral() } dependencies { rust(project(":core:natives")) implementation("org.apache.logging.log4j:log4j-api:2.17.0") implementation("org.apache.commons:commons-lang3:3.12.0") implementation("com.google.code.gson:gson:2.8.9") implementation("org.lwjgl:lwjgl-glfw:3.3.1") testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") } rustImport { baseDir.set("/graphics/kiln/blaze4d/core/natives") layout.set("hierarchical") } tasks.withType { options.release.set(18) options.compilerArgs.add("--add-modules=jdk.incubator.foreign") } tasks.getByName("test") { useJUnitPlatform() } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/Blaze4DCore.java ================================================ package graphics.kiln.blaze4d.core; import graphics.kiln.blaze4d.core.natives.Natives; import graphics.kiln.blaze4d.core.types.B4DFormat; import graphics.kiln.blaze4d.core.types.B4DImageData; import graphics.kiln.blaze4d.core.types.B4DMeshData; import graphics.kiln.blaze4d.core.types.B4DVertexFormat; import jdk.incubator.foreign.MemoryAddress; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.StringFormatterMessageFactory; public class Blaze4DCore implements AutoCloseable { public static final Logger LOGGER = LogManager.getLogger("Blaze4DCore", new StringFormatterMessageFactory()); private final MemoryAddress handle; public Blaze4DCore(long glfwWindow) { boolean enableValidation = System.getProperty("b4d.enable_validation") != null; MemoryAddress surfaceProvider = Natives.b4dCreateGlfwSurfaceProvider(glfwWindow); this.handle = Natives.b4dInit(surfaceProvider, enableValidation); } public void setDebugMode(DebugMode mode) { Natives.b4dSetDebugMode(this.handle, mode.raw); } public long createShader(B4DVertexFormat vertexFormat, long usedUniforms) { return Natives.b4dCreateShader(this.handle, vertexFormat.getAddress(), usedUniforms); } public void destroyShader(long shaderId) { Natives.b4dDestroyShader(this.handle, shaderId); } public GlobalMesh createGlobalMesh(B4DMeshData meshData) { return new GlobalMesh(Natives.b4dCreateGlobalMesh(this.handle, meshData.getAddress())); } public GlobalImage createGlobalImage(int width, int height, B4DFormat format) { return new GlobalImage(Natives.b4dCreateGlobalImage(this.handle, width, height, format.getValue())); } public Frame startFrame(int windowWidth, int windowHeight) { MemoryAddress frame = Natives.b4dStartFrame(this.handle, windowWidth, windowHeight); if(frame.toRawLongValue() == 0L) { return null; } else { return new Frame(frame); } } @Override public void close() throws Exception { Natives.b4dDestroy(this.handle); } public enum DebugMode { NONE(0), DEPTH(1), POSITION(2), COLOR(3), NORMAL(4), UV0(5), UV1(6), UV2(7), TEXTURED0(8), TEXTURED1(9), TEXTURED2(10); final int raw; DebugMode(int raw) { this.raw = raw; } } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/Frame.java ================================================ package graphics.kiln.blaze4d.core; import graphics.kiln.blaze4d.core.natives.Natives; import graphics.kiln.blaze4d.core.types.B4DMeshData; import graphics.kiln.blaze4d.core.types.B4DUniformData; import jdk.incubator.foreign.MemoryAddress; public class Frame implements AutoCloseable { private final MemoryAddress handle; Frame(MemoryAddress handle) { this.handle = handle; } public void updateUniform(long shaderId, B4DUniformData data) { Natives.b4dPassUpdateUniform(this.handle, data.getAddress(), shaderId); } public void drawGlobal(GlobalMesh mesh, long shaderId, boolean depthWrite) { Natives.b4dPassDrawGlobal(this.handle, mesh.getHandle(), shaderId, depthWrite); } public int uploadImmediate(B4DMeshData data) { return Natives.b4dPassUploadImmediate(this.handle, data.getAddress()); } public void drawImmediate(int meshId, long shaderId, boolean depthWrite) { Natives.b4dPassDrawImmediate(this.handle, meshId, shaderId, depthWrite); } @Override public void close() throws Exception { Natives.b4dEndFrame(this.handle); } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/GlobalImage.java ================================================ package graphics.kiln.blaze4d.core; import graphics.kiln.blaze4d.core.natives.Natives; import graphics.kiln.blaze4d.core.types.B4DImageData; import jdk.incubator.foreign.MemoryAddress; public class GlobalImage implements AutoCloseable { private final MemoryAddress handle; GlobalImage(MemoryAddress handle) { this.handle = handle; } public void update(B4DImageData data) { Natives.b4DUpdateGlobalImage(this.handle, data.getAddress(), 1); } MemoryAddress getHandle() { return this.handle; } @Override public void close() throws Exception { Natives.b4dDestroyGlobalImage(this.handle); } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/GlobalMesh.java ================================================ package graphics.kiln.blaze4d.core; import graphics.kiln.blaze4d.core.natives.Natives; import jdk.incubator.foreign.MemoryAddress; public class GlobalMesh implements AutoCloseable { private final MemoryAddress handle; GlobalMesh(MemoryAddress handle) { this.handle = handle; } MemoryAddress getHandle() { return this.handle; } @Override public void close() throws Exception { Natives.b4dDestroyGlobalMesh(this.handle); } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/natives/ImageDataNative.java ================================================ package graphics.kiln.blaze4d.core.natives; import jdk.incubator.foreign.MemoryLayout; import jdk.incubator.foreign.ValueLayout; import java.lang.invoke.VarHandle; public class ImageDataNative { public static final MemoryLayout LAYOUT; public static final MemoryLayout.PathElement DATA_PTR_PATH; public static final MemoryLayout.PathElement DATA_LEN_PATH; public static final MemoryLayout.PathElement ROW_STRIDE_PATH; public static final MemoryLayout.PathElement OFFSET_PATH; public static final MemoryLayout.PathElement EXTENT_PATH; public static final VarHandle DATA_PTR_HANDLE; public static final VarHandle DATA_LEN_HANDLE; public static final VarHandle ROW_STRIDE_HANDLE; public static final VarHandle OFFSET_HANDLE; public static final VarHandle EXTENT_HANDLE; static { LAYOUT = MemoryLayout.structLayout( ValueLayout.ADDRESS.withName("data_ptr"), Natives.getSizeLayout().withName("data_len"), ValueLayout.JAVA_INT.withName("row_stride"), MemoryLayout.sequenceLayout(2, ValueLayout.JAVA_INT).withName("offset"), MemoryLayout.sequenceLayout(2, ValueLayout.JAVA_INT).withName("extent") ); DATA_PTR_PATH = MemoryLayout.PathElement.groupElement("data_ptr"); DATA_LEN_PATH = MemoryLayout.PathElement.groupElement("data_len"); ROW_STRIDE_PATH = MemoryLayout.PathElement.groupElement("row_stride"); OFFSET_PATH = MemoryLayout.PathElement.groupElement("offset"); EXTENT_PATH = MemoryLayout.PathElement.groupElement("extent"); DATA_PTR_HANDLE = LAYOUT.varHandle(DATA_PTR_PATH); DATA_LEN_HANDLE = LAYOUT.varHandle(DATA_LEN_PATH); ROW_STRIDE_HANDLE = LAYOUT.varHandle(ROW_STRIDE_PATH); OFFSET_HANDLE = LAYOUT.varHandle(OFFSET_PATH, MemoryLayout.PathElement.sequenceElement()); EXTENT_HANDLE = LAYOUT.varHandle(EXTENT_PATH, MemoryLayout.PathElement.sequenceElement()); } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/natives/Lib.java ================================================ package graphics.kiln.blaze4d.core.natives; import graphics.kiln.blaze4d.core.Blaze4DCore; import jdk.incubator.foreign.SymbolLookup; import org.apache.commons.lang3.SystemUtils; import java.io.*; /** * Manages loading of the native library */ public class Lib { public static SymbolLookup nativeLookup = null; /** * Attempts to load the b4d core natives. * * It is safe to call this function multiple times and concurrently. */ public static synchronized void loadNatives() { if (nativeLookup != null) { return; } String overwrite = System.getProperty("b4d_core.native_lib"); if (overwrite != null) { File overwriteFile = new File(overwrite).getAbsoluteFile(); Blaze4DCore.LOGGER.info("Using native overwrite: " + overwriteFile); System.load(overwriteFile.getAbsolutePath()); nativeLookup = SymbolLookup.loaderLookup(); return; } File nativeFile = extractNatives(new File(System.getProperty("user.dir"), "b4d" + File.separator + "natives")); System.load(nativeFile.getAbsolutePath()); nativeLookup = SymbolLookup.loaderLookup(); } private static File extractNatives(File dstDirectory) { if (!dstDirectory.exists()) { if (!dstDirectory.mkdirs()) { throw new RuntimeException("Failed to make natives directory"); } } if (!dstDirectory.isDirectory()) { throw new RuntimeException("Natives directory is not a directory"); } String fileName = System.mapLibraryName("b4d-core"); File nativesFile = new File(dstDirectory, fileName); Blaze4DCore.LOGGER.info("Extracting natives to " + nativesFile); if(nativesFile.isFile()) { if (!nativesFile.delete()) { throw new RuntimeException("Failed to delete already existing natives file"); } } else { if (nativesFile.exists()) { throw new RuntimeException("Natives file already exists but is not a file"); } } copyToFile(nativesFile, Os.getOs().name + "/" + Arch.getArch().name + "/" + fileName); return nativesFile; } private static void copyToFile(File dst, String resourcePath) { try (InputStream in = Lib.class.getResourceAsStream(resourcePath)) { if (in == null) { throw new RuntimeException("Invalid native lib resource path: " + resourcePath); } try (OutputStream out = new FileOutputStream(dst)) { byte[] buffer = new byte[1024 * 1024 * 16]; int readBytes; while((readBytes = in.read(buffer)) != -1) { out.write(buffer, 0, readBytes); } } } catch (IOException e) { throw new RuntimeException("Failed to extract native lib.", e); } } private enum Os { WINDOWS("windows"), GENERIC_LINUX("generic_linux"), MAC("mac"); public final String name; Os(String name) { this.name = name; } static Os getOs() { if (SystemUtils.IS_OS_WINDOWS) { return WINDOWS; } else if(SystemUtils.IS_OS_LINUX) { return GENERIC_LINUX; } else if(SystemUtils.IS_OS_MAC) { return MAC; } else { throw new UnsupportedOperationException("Unknown os: " + SystemUtils.OS_NAME); } } } private enum Arch { I686("i686"), AMD64("x86_64"), AARCH64("aarch64"); public final String name; Arch(String name) { this.name = name; } static Arch getArch() { return switch (SystemUtils.OS_ARCH) { case "x86" -> I686; case "amd64" -> AMD64; case "aarch64" -> AARCH64; default -> throw new UnsupportedOperationException("Unknown arch: " + SystemUtils.OS_ARCH); }; } } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/natives/McUniformDataNative.java ================================================ package graphics.kiln.blaze4d.core.natives; import jdk.incubator.foreign.*; import java.lang.invoke.VarHandle; public class McUniformDataNative { public static final MemoryLayout LAYOUT; public static final MemoryLayout.PathElement UNIFORM_PATH; public static final MemoryLayout.PathElement PAYLOAD_PATH; public static final MemoryLayout.PathElement PAYLOAD_U32_PATH; public static final MemoryLayout.PathElement PAYLOAD_F32_PATH; public static final MemoryLayout.PathElement PAYLOAD_VEC2F32_PATH; public static final MemoryLayout.PathElement PAYLOAD_VEC3F32_PATH; public static final MemoryLayout.PathElement PAYLOAD_VEC4F32_PATH; public static final MemoryLayout.PathElement PAYLOAD_MAT4F32_PATH; public static final VarHandle UNIFORM_HANDLE; public static final VarHandle PAYLOAD_U32_HANDLE; public static final VarHandle PAYLOAD_F32_HANDLE; public static final VarHandle PAYLOAD_VEC2F32_HANDLE; public static final VarHandle PAYLOAD_VEC3F32_HANDLE; public static final VarHandle PAYLOAD_VEC4F32_HANDLE; public static final VarHandle PAYLOAD_MAT4F32_HANDLE; static { LAYOUT = MemoryLayout.structLayout( ValueLayout.JAVA_LONG.withName("uniform"), MemoryLayout.unionLayout( ValueLayout.JAVA_INT.withName("u32"), ValueLayout.JAVA_FLOAT.withName("f32"), MemoryLayout.sequenceLayout(2, ValueLayout.JAVA_FLOAT).withName("vec2f32"), MemoryLayout.sequenceLayout(3, ValueLayout.JAVA_FLOAT).withName("vec3f32"), MemoryLayout.sequenceLayout(4, ValueLayout.JAVA_FLOAT).withName("vec4f32"), MemoryLayout.sequenceLayout(16, ValueLayout.JAVA_FLOAT).withName("mat4f32") ).withName("payload") ); UNIFORM_PATH = MemoryLayout.PathElement.groupElement("uniform"); PAYLOAD_PATH = MemoryLayout.PathElement.groupElement("payload"); PAYLOAD_U32_PATH = MemoryLayout.PathElement.groupElement("u32"); PAYLOAD_F32_PATH = MemoryLayout.PathElement.groupElement("f32"); PAYLOAD_VEC2F32_PATH = MemoryLayout.PathElement.groupElement("vec2f32"); PAYLOAD_VEC3F32_PATH = MemoryLayout.PathElement.groupElement("vec3f32"); PAYLOAD_VEC4F32_PATH = MemoryLayout.PathElement.groupElement("vec4f32"); PAYLOAD_MAT4F32_PATH = MemoryLayout.PathElement.groupElement("mat4f32"); UNIFORM_HANDLE = LAYOUT.varHandle(UNIFORM_PATH); PAYLOAD_U32_HANDLE = LAYOUT.varHandle(PAYLOAD_PATH, PAYLOAD_U32_PATH); PAYLOAD_F32_HANDLE = LAYOUT.varHandle(PAYLOAD_PATH, PAYLOAD_F32_PATH); PAYLOAD_VEC2F32_HANDLE = LAYOUT.varHandle(PAYLOAD_PATH, PAYLOAD_VEC2F32_PATH, MemoryLayout.PathElement.sequenceElement()); PAYLOAD_VEC3F32_HANDLE = LAYOUT.varHandle(PAYLOAD_PATH, PAYLOAD_VEC3F32_PATH, MemoryLayout.PathElement.sequenceElement()); PAYLOAD_VEC4F32_HANDLE = LAYOUT.varHandle(PAYLOAD_PATH, PAYLOAD_VEC4F32_PATH, MemoryLayout.PathElement.sequenceElement()); PAYLOAD_MAT4F32_HANDLE = LAYOUT.varHandle(PAYLOAD_PATH, PAYLOAD_MAT4F32_PATH, MemoryLayout.PathElement.sequenceElement()); } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/natives/MeshDataNative.java ================================================ package graphics.kiln.blaze4d.core.natives; import jdk.incubator.foreign.*; import java.lang.invoke.VarHandle; public class MeshDataNative { public static final MemoryLayout LAYOUT; public static final MemoryLayout.PathElement VERTEX_DATA_PTR_PATH; public static final MemoryLayout.PathElement VERTEX_DATA_LEN_PATH; public static final MemoryLayout.PathElement INDEX_DATA_PTR_PATH; public static final MemoryLayout.PathElement INDEX_DATA_LEN_PATH; public static final MemoryLayout.PathElement VERTEX_STRIDE_PATH; public static final MemoryLayout.PathElement INDEX_COUNT_PATH; public static final MemoryLayout.PathElement INDEX_TYPE_PATH; public static final MemoryLayout.PathElement PRIMITIVE_TOPOLOGY_PATH; public static final VarHandle VERTEX_DATA_PTR_HANDLE; public static final VarHandle VERTEX_DATA_LEN_HANDLE; public static final VarHandle INDEX_DATA_PTR_HANDLE; public static final VarHandle INDEX_DATA_LEN_HANDLE; public static final VarHandle VERTEX_STRIDE_HANDLE; public static final VarHandle INDEX_COUNT_HANDLE; public static final VarHandle INDEX_TYPE_HANDLE; public static final VarHandle PRIMITIVE_TOPOLOGY_HANDLE; static { LAYOUT = MemoryLayout.structLayout( ValueLayout.ADDRESS.withName("vertex_data_ptr"), Natives.getSizeLayout().withName("vertex_data_len"), ValueLayout.ADDRESS.withName("index_data_ptr"), Natives.getSizeLayout().withName("index_data_len"), ValueLayout.JAVA_INT.withName("vertex_stride"), ValueLayout.JAVA_INT.withName("index_count"), ValueLayout.JAVA_INT.withName("index_type"), ValueLayout.JAVA_INT.withName("primitive_topology") ); VERTEX_DATA_PTR_PATH = MemoryLayout.PathElement.groupElement("vertex_data_ptr"); VERTEX_DATA_LEN_PATH = MemoryLayout.PathElement.groupElement("vertex_data_len"); INDEX_DATA_PTR_PATH = MemoryLayout.PathElement.groupElement("index_data_ptr"); INDEX_DATA_LEN_PATH = MemoryLayout.PathElement.groupElement("index_data_len"); VERTEX_STRIDE_PATH = MemoryLayout.PathElement.groupElement("vertex_stride"); INDEX_COUNT_PATH = MemoryLayout.PathElement.groupElement("index_count"); INDEX_TYPE_PATH = MemoryLayout.PathElement.groupElement("index_type"); PRIMITIVE_TOPOLOGY_PATH = MemoryLayout.PathElement.groupElement("primitive_topology"); VERTEX_DATA_PTR_HANDLE = LAYOUT.varHandle(VERTEX_DATA_PTR_PATH); VERTEX_DATA_LEN_HANDLE = LAYOUT.varHandle(VERTEX_DATA_LEN_PATH); INDEX_DATA_PTR_HANDLE = LAYOUT.varHandle(INDEX_DATA_PTR_PATH); INDEX_DATA_LEN_HANDLE = LAYOUT.varHandle(INDEX_DATA_LEN_PATH); VERTEX_STRIDE_HANDLE = LAYOUT.varHandle(VERTEX_STRIDE_PATH); INDEX_COUNT_HANDLE = LAYOUT.varHandle(INDEX_COUNT_PATH); INDEX_TYPE_HANDLE = LAYOUT.varHandle(INDEX_TYPE_PATH); PRIMITIVE_TOPOLOGY_HANDLE = LAYOUT.varHandle(PRIMITIVE_TOPOLOGY_PATH); } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/natives/Natives.java ================================================ /** * Internal api to directly call native functions */ package graphics.kiln.blaze4d.core.natives; import jdk.incubator.foreign.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.StringFormatterMessageFactory; import org.lwjgl.glfw.GLFW; import org.lwjgl.system.APIUtil; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.nio.charset.StandardCharsets; import java.util.Optional; import static jdk.incubator.foreign.ValueLayout.*; public class Natives { private static final Logger NATIVE_LOGGER = LogManager.getLogger("Blaze4DNative", new StringFormatterMessageFactory()); public static final CLinker linker; public static final NativeMetadata nativeMetadata; public static final MethodHandle B4D_CREATE_GLFW_SURFACE_PROVIDER_HANDLE; public static final MethodHandle B4D_INIT_HANDLE; public static final MethodHandle B4D_DESTROY_HANDLE; public static final MethodHandle B4D_SET_DEBUG_MODE_HANDLE; public static final MethodHandle B4D_CREATE_GLOBAL_MESH_HANDLE; public static final MethodHandle B4D_DESTROY_GLOBAL_MESH_HANDLE; public static final MethodHandle B4D_CREATE_GLOBAL_IMAGE_HANDLE; public static final MethodHandle B4D_UPDATE_GLOBAL_IMAGE_HANDLE; public static final MethodHandle B4D_DESTROY_GLOBAL_IMAGE_HANDLE; public static final MethodHandle B4D_CREATE_SHADER_HANDLE; public static final MethodHandle B4D_DESTROY_SHADER_HANDLE; public static final MethodHandle B4D_START_FRAME_HANDLE; public static final MethodHandle B4D_PASS_UPDATE_UNIFORM_HANDLE; public static final MethodHandle B4D_PASS_DRAW_GLOBAL_HANDLE; public static final MethodHandle B4D_PASS_UPLOAD_IMMEDIATE_HANDLE; public static final MethodHandle B4D_PASS_DRAW_IMMEDIATE_HANDLE; public static final MethodHandle B4D_END_FRAME_HANDLE; static { Lib.loadNatives(); linker = CLinker.systemCLinker(); nativeMetadata = loadMetadata(); initNativeLogger(); preInitGlfw(); B4D_CREATE_GLFW_SURFACE_PROVIDER_HANDLE = lookupFunction("b4d_create_glfw_surface_provider", FunctionDescriptor.of(ADDRESS, ADDRESS, ADDRESS, ADDRESS) ); B4D_INIT_HANDLE = lookupFunction("b4d_init", FunctionDescriptor.of(ADDRESS, ADDRESS, JAVA_INT) ); B4D_DESTROY_HANDLE = lookupFunction("b4d_destroy", FunctionDescriptor.ofVoid(ADDRESS) ); B4D_SET_DEBUG_MODE_HANDLE = lookupFunction("b4d_set_debug_mode", FunctionDescriptor.ofVoid(ADDRESS, JAVA_INT) ); B4D_CREATE_GLOBAL_MESH_HANDLE = lookupFunction("b4d_create_global_mesh", FunctionDescriptor.of(ADDRESS, ADDRESS, ADDRESS) ); B4D_DESTROY_GLOBAL_MESH_HANDLE = lookupFunction("b4d_destroy_global_mesh", FunctionDescriptor.ofVoid(ADDRESS) ); B4D_CREATE_GLOBAL_IMAGE_HANDLE = lookupFunction("b4d_create_global_image", FunctionDescriptor.of(ADDRESS, JAVA_INT, JAVA_INT, JAVA_INT) ); B4D_UPDATE_GLOBAL_IMAGE_HANDLE = lookupFunction("b4d_update_global_image", FunctionDescriptor.ofVoid(ADDRESS, ADDRESS, JAVA_INT) ); B4D_DESTROY_GLOBAL_IMAGE_HANDLE = lookupFunction("b4d_destroy_global_image", FunctionDescriptor.ofVoid(ADDRESS) ); B4D_CREATE_SHADER_HANDLE = lookupFunction("b4d_create_shader", FunctionDescriptor.of(JAVA_LONG, ADDRESS, ADDRESS, JAVA_LONG) ); B4D_DESTROY_SHADER_HANDLE = lookupFunction("b4d_destroy_shader", FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG) ); B4D_START_FRAME_HANDLE = lookupFunction("b4d_start_frame", FunctionDescriptor.of(ADDRESS, ADDRESS, JAVA_INT, JAVA_INT) ); B4D_PASS_UPDATE_UNIFORM_HANDLE = lookupFunction("b4d_pass_update_uniform", FunctionDescriptor.ofVoid(ADDRESS, ADDRESS, JAVA_LONG) ); B4D_PASS_DRAW_GLOBAL_HANDLE = lookupFunction("b4d_pass_draw_global", FunctionDescriptor.ofVoid(ADDRESS, ADDRESS, JAVA_LONG, JAVA_INT) ); B4D_PASS_UPLOAD_IMMEDIATE_HANDLE = lookupFunction("b4d_pass_upload_immediate", FunctionDescriptor.of(JAVA_INT, ADDRESS, ADDRESS) ); B4D_PASS_DRAW_IMMEDIATE_HANDLE = lookupFunction("b4d_pass_draw_immediate", FunctionDescriptor.ofVoid(ADDRESS, JAVA_INT, JAVA_LONG, JAVA_INT) ); B4D_END_FRAME_HANDLE = lookupFunction("b4d_end_frame", FunctionDescriptor.ofVoid(ADDRESS) ); } public static MemoryAddress b4dCreateGlfwSurfaceProvider(long glfwWindow) { MemoryAddress pfnGlfwGetRequiredInstanceExtensions = MemoryAddress.ofLong(APIUtil.apiGetFunctionAddress(GLFW.getLibrary(), "glfwGetRequiredInstanceExtensions")); MemoryAddress pfnGlfwCreateWindowSurface = MemoryAddress.ofLong(APIUtil.apiGetFunctionAddress(GLFW.getLibrary(), "glfwCreateWindowSurface")); try { return (MemoryAddress) B4D_CREATE_GLFW_SURFACE_PROVIDER_HANDLE.invoke(MemoryAddress.ofLong(glfwWindow), pfnGlfwGetRequiredInstanceExtensions, pfnGlfwCreateWindowSurface); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_create_glfw_surface_provider", e); } } public static MemoryAddress b4dInit(MemoryAddress surface, boolean enableValidation) { int enableValidationInt = enableValidation ? 1 : 0; try { return (MemoryAddress) B4D_INIT_HANDLE.invoke(surface, enableValidationInt); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_init", e); } } public static void b4dDestroy(MemoryAddress b4d) { try { B4D_DESTROY_HANDLE.invoke(b4d); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_destroy", e); } } public static void b4dSetDebugMode(MemoryAddress b4d, int debugMode) { try { B4D_SET_DEBUG_MODE_HANDLE.invoke(b4d, debugMode); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_set_debug_mode", e); } } public static MemoryAddress b4dCreateGlobalMesh(MemoryAddress b4d, MemoryAddress meshData) { try { return (MemoryAddress) B4D_CREATE_GLOBAL_MESH_HANDLE.invoke(b4d, meshData); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_create_global_mesh", e); } } public static void b4dDestroyGlobalMesh(MemoryAddress mesh) { try { B4D_DESTROY_GLOBAL_MESH_HANDLE.invoke(mesh); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_destroy_global_mesh", e); } } public static MemoryAddress b4dCreateGlobalImage(MemoryAddress b4d, int width, int height, int format) { try { return (MemoryAddress) B4D_CREATE_GLOBAL_IMAGE_HANDLE.invoke(b4d, width, height, format); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_create_global_image", e); } } public static void b4DUpdateGlobalImage(MemoryAddress image, MemoryAddress data, int dataCount) { try { B4D_UPDATE_GLOBAL_IMAGE_HANDLE.invoke(image, data, dataCount); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_update_global_image", e); } } public static void b4dDestroyGlobalImage(MemoryAddress image) { try { B4D_DESTROY_GLOBAL_IMAGE_HANDLE.invoke(image); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_destroy_global_image", e); } } public static long b4dCreateShader(MemoryAddress b4d, MemoryAddress vertexFormat, long usedUniforms) { try { return (long) B4D_CREATE_SHADER_HANDLE.invoke(b4d, vertexFormat, usedUniforms); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_create_shader", e); } } public static void b4dDestroyShader(MemoryAddress b4d, long shaderId) { try { B4D_DESTROY_SHADER_HANDLE.invoke(b4d, shaderId); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_destroy_shader", e); } } public static MemoryAddress b4dStartFrame(MemoryAddress b4d, int windowWidth, int windowHeight) { try { return (MemoryAddress) B4D_START_FRAME_HANDLE.invoke(b4d, windowWidth, windowHeight); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_start_frame", e); } } public static void b4dPassUpdateUniform(MemoryAddress frame, MemoryAddress data, long shaderId) { try { B4D_PASS_UPDATE_UNIFORM_HANDLE.invoke(frame, data, shaderId); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_pass_update_uniform", e); } } public static void b4dPassDrawGlobal(MemoryAddress frame, MemoryAddress mesh, long shaderId, boolean depthWrite) { int depthWriteInt; if (depthWrite) { depthWriteInt = 1; } else { depthWriteInt = 0; } try { B4D_PASS_DRAW_GLOBAL_HANDLE.invoke(frame, mesh, shaderId, depthWriteInt); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_pass_draw_global", e); } } public static int b4dPassUploadImmediate(MemoryAddress frame, MemoryAddress data) { try { return (int) B4D_PASS_UPLOAD_IMMEDIATE_HANDLE.invoke(frame, data); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_pass_upload_immediate", e); } } public static void b4dPassDrawImmediate(MemoryAddress frame, int meshId, long shaderId, boolean depthWrite) { int depthWriteInt; if (depthWrite) { depthWriteInt = 1; } else { depthWriteInt = 0; } try { B4D_PASS_DRAW_IMMEDIATE_HANDLE.invoke(frame, meshId, shaderId, depthWriteInt); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_pass_draw_immediate", e); } } public static void b4dEndFrame(MemoryAddress frame) { try { B4D_END_FRAME_HANDLE.invoke(frame); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_end_frame", e); } } public record NativeMetadata(int sizeBytes) { } public static ValueLayout getSizeLayout() { // Only 64 bit is supported right now return JAVA_LONG; } private static MethodHandle lookupFunction(String name, FunctionDescriptor descriptor) { Optional result = Lib.nativeLookup.lookup(name); if (result.isPresent()) { return linker.downcallHandle(result.get(), descriptor); } throw new UnsatisfiedLinkError("Failed to find Blaze4D core function \"" + name + "\""); } private static NativeMetadata loadMetadata() { MethodHandle b4dGetNativeMetadataHandle = lookupFunction("b4d_get_native_metadata", FunctionDescriptor.of(ADDRESS) ); MemoryLayout layout = MemoryLayout.structLayout( JAVA_INT.withName("size_bytes") ); MemoryAddress address; try { address = (MemoryAddress) b4dGetNativeMetadataHandle.invoke(); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_get_native_metadata", e); } MemorySegment segment = MemorySegment.ofAddress(address, layout.byteSize(), ResourceScope.globalScope()); int sizeBytes = segment.get(JAVA_INT, layout.byteOffset(PathElement.groupElement("size_bytes"))); if(sizeBytes != 8) { throw new RuntimeException("Blaze4D natives have 4byte size type. We do not support 32bit right now."); } return new NativeMetadata(sizeBytes); } private static void preInitGlfw() { MethodHandle b4dPreInitGlfwHandle = lookupFunction("b4d_pre_init_glfw", FunctionDescriptor.ofVoid(ADDRESS) ); try { b4dPreInitGlfwHandle.invoke(MemoryAddress.ofLong(APIUtil.apiGetFunctionAddress(GLFW.getLibrary(), "glfwInitVulkanLoader"))); } catch (Throwable e) { throw new RuntimeException("Failed to invoke b4d_pre_init_glfw", e); } } private static void initNativeLogger() { MethodHandle b4dInitExternalLogger = lookupFunction("b4d_init_external_logger", FunctionDescriptor.ofVoid(ADDRESS) ); try { MethodHandle logFn = MethodHandles.lookup().findStatic(Natives.class, "nativeLogHandler", MethodType.methodType(Void.TYPE, MemoryAddress.class, MemoryAddress.class, Integer.TYPE, Integer.TYPE, Integer.TYPE)); NativeSymbol logFnNative = linker.upcallStub( logFn, FunctionDescriptor.ofVoid(ADDRESS, ADDRESS, JAVA_INT, JAVA_INT, JAVA_INT), ResourceScope.globalScope() ); b4dInitExternalLogger.invoke(logFnNative); } catch (Throwable e) { throw new RuntimeException("Failed to init b4d native logger", e); } } private static void nativeLogHandler(MemoryAddress targetPtr, MemoryAddress msgPtr, int targetLen, int msgLen, int level) { try (ResourceScope scope = ResourceScope.newConfinedScope()) { MemorySegment target = MemorySegment.ofAddress(targetPtr, targetLen, scope); MemorySegment message = MemorySegment.ofAddress(msgPtr, msgLen, scope); byte[] targetData = target.toArray(ValueLayout.JAVA_BYTE); byte[] messageData = message.toArray(ValueLayout.JAVA_BYTE); String targetString = new String(targetData, StandardCharsets.UTF_8); String messageString = new String(messageData, StandardCharsets.UTF_8); switch (level) { case 0 -> NATIVE_LOGGER.trace(messageString); case 1 -> NATIVE_LOGGER.debug(messageString); case 2 -> NATIVE_LOGGER.info(messageString); case 3 -> NATIVE_LOGGER.warn(messageString); case 4 -> NATIVE_LOGGER.error(messageString); default -> NATIVE_LOGGER.error("Received invalid log level from b4d native: " + level); } } catch (Throwable e) { NATIVE_LOGGER.error("Failed to log native message", e); } } public static void verifyInit() { } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/natives/PipelineConfigurationNative.java ================================================ package graphics.kiln.blaze4d.core.natives; import jdk.incubator.foreign.MemoryLayout; import jdk.incubator.foreign.ValueLayout; import java.lang.invoke.VarHandle; public class PipelineConfigurationNative { public static final MemoryLayout LAYOUT; public static final MemoryLayout.PathElement DEPTH_TEST_ENABLE_PATH; public static final MemoryLayout.PathElement DEPTH_COMPARE_OP_PATH; public static final MemoryLayout.PathElement DEPTH_WRITE_ENABLE_PATH; public static final MemoryLayout.PathElement BLEND_ENABLE_PATH; public static final MemoryLayout.PathElement BLEND_COLOR_OP_PATH; public static final MemoryLayout.PathElement BLEND_COLOR_SRC_FACTOR_PATH; public static final MemoryLayout.PathElement BLEND_COLOR_DST_FACTOR_PATH; public static final MemoryLayout.PathElement BLEND_ALPHA_OP_PATH; public static final MemoryLayout.PathElement BLEND_ALPHA_SRC_FACTOR_PATH; public static final MemoryLayout.PathElement BLEND_ALPHA_DST_FACTOR_PATH; public static final VarHandle DEPTH_TEST_ENABLE_HANDLE; public static final VarHandle DEPTH_COMPARE_OP_HANDLE; public static final VarHandle DEPTH_WRITE_ENABLE_HANDLE; public static final VarHandle BLEND_ENABLE_HANDLE; public static final VarHandle BLEND_COLOR_OP_HANDLE; public static final VarHandle BLEND_COLOR_SRC_FACTOR_HANDLE; public static final VarHandle BLEND_COLOR_DST_FACTOR_HANDLE; public static final VarHandle BLEND_ALPHA_OP_HANDLE; public static final VarHandle BLEND_ALPHA_SRC_FACTOR_HANDLE; public static final VarHandle BLEND_ALPHA_DST_FACTOR_HANDLE; static { LAYOUT = MemoryLayout.structLayout( ValueLayout.JAVA_INT.withName("depth_test_enable"), ValueLayout.JAVA_INT.withName("depth_compare_op"), ValueLayout.JAVA_INT.withName("depth_write_enable"), ValueLayout.JAVA_INT.withName("blend_enable"), ValueLayout.JAVA_INT.withName("blend_color_op"), ValueLayout.JAVA_INT.withName("blend_color_src_factor"), ValueLayout.JAVA_INT.withName("blend_color_dst_factor"), ValueLayout.JAVA_INT.withName("blend_alpha_op"), ValueLayout.JAVA_INT.withName("blend_alpha_src_factor"), ValueLayout.JAVA_INT.withName("blend_alpha_dst_factor") ); DEPTH_TEST_ENABLE_PATH = MemoryLayout.PathElement.groupElement("depth_test_enable"); DEPTH_COMPARE_OP_PATH = MemoryLayout.PathElement.groupElement("depth_compare_op"); DEPTH_WRITE_ENABLE_PATH = MemoryLayout.PathElement.groupElement("depth_write_enable"); BLEND_ENABLE_PATH = MemoryLayout.PathElement.groupElement("blend_enable"); BLEND_COLOR_OP_PATH = MemoryLayout.PathElement.groupElement("blend_color_op"); BLEND_COLOR_SRC_FACTOR_PATH = MemoryLayout.PathElement.groupElement("blend_color_src_factor"); BLEND_COLOR_DST_FACTOR_PATH = MemoryLayout.PathElement.groupElement("blend_color_dst_factor"); BLEND_ALPHA_OP_PATH = MemoryLayout.PathElement.groupElement("blend_alpha_op"); BLEND_ALPHA_SRC_FACTOR_PATH = MemoryLayout.PathElement.groupElement("blend_alpha_src_factor"); BLEND_ALPHA_DST_FACTOR_PATH = MemoryLayout.PathElement.groupElement("blend_alpha_dst_factor"); DEPTH_TEST_ENABLE_HANDLE = LAYOUT.varHandle(DEPTH_TEST_ENABLE_PATH); DEPTH_COMPARE_OP_HANDLE = LAYOUT.varHandle(DEPTH_COMPARE_OP_PATH); DEPTH_WRITE_ENABLE_HANDLE = LAYOUT.varHandle(DEPTH_WRITE_ENABLE_PATH); BLEND_ENABLE_HANDLE = LAYOUT.varHandle(BLEND_ENABLE_PATH); BLEND_COLOR_OP_HANDLE = LAYOUT.varHandle(BLEND_COLOR_OP_PATH); BLEND_COLOR_SRC_FACTOR_HANDLE = LAYOUT.varHandle(BLEND_COLOR_SRC_FACTOR_PATH); BLEND_COLOR_DST_FACTOR_HANDLE = LAYOUT.varHandle(BLEND_COLOR_DST_FACTOR_PATH); BLEND_ALPHA_OP_HANDLE = LAYOUT.varHandle(BLEND_ALPHA_OP_PATH); BLEND_ALPHA_SRC_FACTOR_HANDLE = LAYOUT.varHandle(BLEND_ALPHA_SRC_FACTOR_PATH); BLEND_ALPHA_DST_FACTOR_HANDLE = LAYOUT.varHandle(BLEND_ALPHA_DST_FACTOR_PATH); } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/natives/Vec2u32Native.java ================================================ package graphics.kiln.blaze4d.core.natives; import jdk.incubator.foreign.MemoryLayout; import jdk.incubator.foreign.ValueLayout; public class Vec2u32Native { public static final MemoryLayout LAYOUT; static { LAYOUT = MemoryLayout.sequenceLayout(2, ValueLayout.JAVA_INT); } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/natives/VertexFormatNative.java ================================================ package graphics.kiln.blaze4d.core.natives; import graphics.kiln.blaze4d.core.Blaze4DCore; import jdk.incubator.foreign.*; import java.lang.invoke.VarHandle; public class VertexFormatNative { public static final MemoryLayout LAYOUT; public static final MemoryLayout.PathElement STRIDE_PATH; public static final MemoryLayout.PathElement POSITION_OFFSET_PATH; public static final MemoryLayout.PathElement POSITION_FORMAT_PATH; public static final MemoryLayout.PathElement NORMAL_OFFSET_PATH; public static final MemoryLayout.PathElement NORMAL_FORMAT_PATH; public static final MemoryLayout.PathElement COLOR_OFFSET_PATH; public static final MemoryLayout.PathElement COLOR_FORMAT_PATH; public static final MemoryLayout.PathElement UV0_OFFSET_PATH; public static final MemoryLayout.PathElement UV0_FORMAT_PATH; public static final MemoryLayout.PathElement UV1_OFFSET_PATH; public static final MemoryLayout.PathElement UV1_FORMAT_PATH; public static final MemoryLayout.PathElement UV2_OFFSET_PATH; public static final MemoryLayout.PathElement UV2_FORMAT_PATH; public static final MemoryLayout.PathElement HAS_NORMAL_PATH; public static final MemoryLayout.PathElement HAS_COLOR_PATH; public static final MemoryLayout.PathElement HAS_UV0_PATH; public static final MemoryLayout.PathElement HAS_UV1_PATH; public static final MemoryLayout.PathElement HAS_UV2_PATH; public static final VarHandle STRIDE_HANDLE; public static final VarHandle POSITION_OFFSET_HANDLE; public static final VarHandle POSITION_FORMAT_HANDLE; public static final VarHandle NORMAL_OFFSET_HANDLE; public static final VarHandle NORMAL_FORMAT_HANDLE; public static final VarHandle COLOR_OFFSET_HANDLE; public static final VarHandle COLOR_FORMAT_HANDLE; public static final VarHandle UV0_OFFSET_HANDLE; public static final VarHandle UV0_FORMAT_HANDLE; public static final VarHandle UV1_OFFSET_HANDLE; public static final VarHandle UV1_FORMAT_HANDLE; public static final VarHandle UV2_OFFSET_HANDLE; public static final VarHandle UV2_FORMAT_HANDLE; public static final VarHandle HAS_NORMAL_HANDLE; public static final VarHandle HAS_COLOR_HANDLE; public static final VarHandle HAS_UV0_HANDLE; public static final VarHandle HAS_UV1_HANDLE; public static final VarHandle HAS_UV2_HANDLE; static { LAYOUT = MemoryLayout.structLayout( ValueLayout.JAVA_INT.withName("stride"), ValueLayout.JAVA_INT.withName("position_offset"), ValueLayout.JAVA_INT.withName("position_format"), ValueLayout.JAVA_INT.withName("normal_offset"), ValueLayout.JAVA_INT.withName("normal_format"), ValueLayout.JAVA_INT.withName("color_offset"), ValueLayout.JAVA_INT.withName("color_format"), ValueLayout.JAVA_INT.withName("uv0_offset"), ValueLayout.JAVA_INT.withName("uv0_format"), ValueLayout.JAVA_INT.withName("uv1_offset"), ValueLayout.JAVA_INT.withName("uv1_format"), ValueLayout.JAVA_INT.withName("uv2_offset"), ValueLayout.JAVA_INT.withName("uv2_format"), ValueLayout.JAVA_BOOLEAN.withName("has_normal"), ValueLayout.JAVA_BOOLEAN.withName("has_color"), ValueLayout.JAVA_BOOLEAN.withName("has_uv0"), ValueLayout.JAVA_BOOLEAN.withName("has_uv1"), ValueLayout.JAVA_BOOLEAN.withName("has_uv2") ); STRIDE_PATH = MemoryLayout.PathElement.groupElement("stride"); POSITION_OFFSET_PATH = MemoryLayout.PathElement.groupElement("position_offset"); POSITION_FORMAT_PATH = MemoryLayout.PathElement.groupElement("position_format"); NORMAL_OFFSET_PATH = MemoryLayout.PathElement.groupElement("normal_offset"); NORMAL_FORMAT_PATH = MemoryLayout.PathElement.groupElement("normal_format"); COLOR_OFFSET_PATH = MemoryLayout.PathElement.groupElement("color_offset"); COLOR_FORMAT_PATH = MemoryLayout.PathElement.groupElement("color_format"); UV0_OFFSET_PATH = MemoryLayout.PathElement.groupElement("uv0_offset"); UV0_FORMAT_PATH = MemoryLayout.PathElement.groupElement("uv0_format"); UV1_OFFSET_PATH = MemoryLayout.PathElement.groupElement("uv1_offset"); UV1_FORMAT_PATH = MemoryLayout.PathElement.groupElement("uv1_format"); UV2_OFFSET_PATH = MemoryLayout.PathElement.groupElement("uv2_offset"); UV2_FORMAT_PATH = MemoryLayout.PathElement.groupElement("uv2_format"); HAS_NORMAL_PATH = MemoryLayout.PathElement.groupElement("has_normal"); HAS_COLOR_PATH = MemoryLayout.PathElement.groupElement("has_color"); HAS_UV0_PATH = MemoryLayout.PathElement.groupElement("has_uv0"); HAS_UV1_PATH = MemoryLayout.PathElement.groupElement("has_uv1"); HAS_UV2_PATH = MemoryLayout.PathElement.groupElement("has_uv2"); STRIDE_HANDLE = LAYOUT.varHandle(STRIDE_PATH); POSITION_OFFSET_HANDLE = LAYOUT.varHandle(POSITION_OFFSET_PATH); POSITION_FORMAT_HANDLE = LAYOUT.varHandle(POSITION_FORMAT_PATH); NORMAL_OFFSET_HANDLE = LAYOUT.varHandle(NORMAL_OFFSET_PATH); NORMAL_FORMAT_HANDLE = LAYOUT.varHandle(NORMAL_FORMAT_PATH); COLOR_OFFSET_HANDLE = LAYOUT.varHandle(COLOR_OFFSET_PATH); COLOR_FORMAT_HANDLE = LAYOUT.varHandle(COLOR_FORMAT_PATH); UV0_OFFSET_HANDLE = LAYOUT.varHandle(UV0_OFFSET_PATH); UV0_FORMAT_HANDLE = LAYOUT.varHandle(UV0_FORMAT_PATH); UV1_OFFSET_HANDLE = LAYOUT.varHandle(UV1_OFFSET_PATH); UV1_FORMAT_HANDLE = LAYOUT.varHandle(UV1_FORMAT_PATH); UV2_OFFSET_HANDLE = LAYOUT.varHandle(UV2_OFFSET_PATH); UV2_FORMAT_HANDLE = LAYOUT.varHandle(UV2_FORMAT_PATH); HAS_NORMAL_HANDLE = LAYOUT.varHandle(HAS_NORMAL_PATH); HAS_COLOR_HANDLE = LAYOUT.varHandle(HAS_COLOR_PATH); HAS_UV0_HANDLE = LAYOUT.varHandle(HAS_UV0_PATH); HAS_UV1_HANDLE = LAYOUT.varHandle(HAS_UV1_PATH); HAS_UV2_HANDLE = LAYOUT.varHandle(HAS_UV2_PATH); } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/types/B4DFormat.java ================================================ package graphics.kiln.blaze4d.core.types; public enum B4DFormat { UNDEFINED(0), R8_UNORM(9), R8_SNORM(10), R8_USCALED(11), R8_SSCALED(12), R8_UINT(13), R8_SINT(14), R8_SRGB(15), R8G8_UNORM(16), R8G8_SNORM(17), R8G8_USCALED(18), R8G8_SSCALED(19), R8G8_UINT(20), R8G8_SINT(21), R8G8_SRGB(22), R8G8B8_UNORM(23), R8G8B8_SNORM(24), R8G8B8_USCALED(25), R8G8B8_SSCALED(26), R8G8B8_UINT(27), R8G8B8_SINT(28), R8G8B8_SRGB(29), R8G8B8A8_UNORM(37), R8G8B8A8_SNORM(38), R8G8B8A8_USCALED(39), R8G8B8A8_SSCALED(40), R8G8B8A8_UINT(41), R8G8B8A8_SINT(42), R8G8B8A8_SRGB(43), R16_UNORM(70), R16_SNORM(71), R16_USCALED(72), R16_SSCALED(73), R16_UINT(74), R16_SINT(75), R16_SFLOAT(76), R16G16_UNORM(77), R16G16_SNORM(78), R16G16_USCALED(79), R16G16_SSCALED(80), R16G16_UINT(81), R16G16_SINT(82), R16G16_SFLOAT(83), R16G16B16_UNORM(84), R16G16B16_SNORM(85), R16G16B16_USCALED(86), R16G16B16_SSCALED(87), R16G16B16_UINT(88), R16G16B16_SINT(89), R16G16B16_SFLOAT(90), R16G16B16A16_UNORM(91), R16G16B16A16_SNORM(92), R16G16B16A16_USCALED(93), R16G16B16A16_SSCALED(94), R16G16B16A16_UINT(95), R16G16B16A16_SINT(96), R16G16B16A16_SFLOAT(97), R32_UINT(98), R32_SINT(99), R32_SFLOAT(100), R32G32_UINT(101), R32G32_SINT(102), R32G32_SFLOAT(103), R32G32B32_UINT(104), R32G32B32_SINT(105), R32G32B32_SFLOAT(106), R32G32B32A32_UINT(107), R32G32B32A32_SINT(108), R32G32B32A32_SFLOAT(109); private final int value; B4DFormat(int value) { this.value = value; } public int getValue() { return this.value; } public static B4DFormat fromRaw(int value) { switch (value) { case 0 -> { return B4DFormat.UNDEFINED; } case 9 -> { return B4DFormat.R8_UNORM; } case 10 -> { return B4DFormat.R8_SNORM; } case 11 -> { return B4DFormat.R8_USCALED; } case 12 -> { return B4DFormat.R8_SSCALED; } case 13 -> { return B4DFormat.R8_UINT; } case 14 -> { return B4DFormat.R8_SINT; } case 15 -> { return B4DFormat.R8_SRGB; } case 16 -> { return B4DFormat.R8G8_UNORM; } case 17 -> { return B4DFormat.R8G8_SNORM; } case 18 -> { return B4DFormat.R8G8_USCALED; } case 19 -> { return B4DFormat.R8G8_SSCALED; } case 20 -> { return B4DFormat.R8G8_UINT; } case 21 -> { return B4DFormat.R8G8_SINT; } case 22 -> { return B4DFormat.R8G8_SRGB; } case 23 -> { return B4DFormat.R8G8B8_UNORM; } case 24 -> { return B4DFormat.R8G8B8_SNORM; } case 25 -> { return B4DFormat.R8G8B8_USCALED; } case 26 -> { return B4DFormat.R8G8B8_SSCALED; } case 27 -> { return B4DFormat.R8G8B8_UINT; } case 28 -> { return B4DFormat.R8G8B8_SINT; } case 29 -> { return B4DFormat.R8G8B8_SRGB; } case 37 -> { return B4DFormat.R8G8B8A8_UNORM; } case 38 -> { return B4DFormat.R8G8B8A8_SNORM; } case 39 -> { return B4DFormat.R8G8B8A8_USCALED; } case 40 -> { return B4DFormat.R8G8B8A8_SSCALED; } case 41 -> { return B4DFormat.R8G8B8A8_UINT; } case 42 -> { return B4DFormat.R8G8B8A8_SINT; } case 43 -> { return B4DFormat.R8G8B8A8_SRGB; } case 70 -> { return B4DFormat.R16_UNORM; } case 71 -> { return B4DFormat.R16_SNORM; } case 72 -> { return B4DFormat.R16_USCALED; } case 73 -> { return B4DFormat.R16_SSCALED; } case 74 -> { return B4DFormat.R16_UINT; } case 75 -> { return B4DFormat.R16_SINT; } case 76 -> { return B4DFormat.R16_SFLOAT; } case 77 -> { return B4DFormat.R16G16_UNORM; } case 78 -> { return B4DFormat.R16G16_SNORM; } case 79 -> { return B4DFormat.R16G16_USCALED; } case 80 -> { return B4DFormat.R16G16_SSCALED; } case 81 -> { return B4DFormat.R16G16_UINT; } case 82 -> { return B4DFormat.R16G16_SINT; } case 83 -> { return B4DFormat.R16G16_SFLOAT; } case 84 -> { return B4DFormat.R16G16B16_UNORM; } case 85 -> { return B4DFormat.R16G16B16_SNORM; } case 86 -> { return B4DFormat.R16G16B16_USCALED; } case 87 -> { return B4DFormat.R16G16B16_SSCALED; } case 88 -> { return B4DFormat.R16G16B16_UINT; } case 89 -> { return B4DFormat.R16G16B16_SINT; } case 90 -> { return B4DFormat.R16G16B16_SFLOAT; } case 91 -> { return B4DFormat.R16G16B16A16_UNORM; } case 92 -> { return B4DFormat.R16G16B16A16_SNORM; } case 93 -> { return B4DFormat.R16G16B16A16_USCALED; } case 94 -> { return B4DFormat.R16G16B16A16_SSCALED; } case 95 -> { return B4DFormat.R16G16B16A16_UINT; } case 96 -> { return B4DFormat.R16G16B16A16_SINT; } case 97 -> { return B4DFormat.R16G16B16A16_SFLOAT; } case 98 -> { return B4DFormat.R32_UINT; } case 99 -> { return B4DFormat.R32_SINT; } case 100 -> { return B4DFormat.R32_SFLOAT; } case 101 -> { return B4DFormat.R32G32_UINT; } case 102 -> { return B4DFormat.R32G32_SINT; } case 103 -> { return B4DFormat.R32G32_SFLOAT; } case 104 -> { return B4DFormat.R32G32B32_UINT; } case 105 -> { return B4DFormat.R32G32B32_SINT; } case 106 -> { return B4DFormat.R32G32B32_SFLOAT; } case 107 -> { return B4DFormat.R32G32B32A32_UINT; } case 108 -> { return B4DFormat.R32G32B32A32_SINT; } case 109 -> { return B4DFormat.R32G32B32A32_SFLOAT; } default -> throw new RuntimeException("Invalid format value " + value); } } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/types/B4DImageData.java ================================================ package graphics.kiln.blaze4d.core.types; import graphics.kiln.blaze4d.core.natives.ImageDataNative; import jdk.incubator.foreign.MemoryAddress; import jdk.incubator.foreign.MemorySegment; import jdk.incubator.foreign.ResourceScope; public class B4DImageData implements AutoCloseable { private final ResourceScope resourceScope; private final MemorySegment memory; public B4DImageData() { this.resourceScope = ResourceScope.newSharedScope(); this.memory = MemorySegment.allocateNative(ImageDataNative.LAYOUT, this.resourceScope); } private void setData(MemoryAddress data, long dataLen) { ImageDataNative.DATA_PTR_HANDLE.set(this.memory, data); ImageDataNative.DATA_LEN_HANDLE.set(this.memory, dataLen); } public void setData(long dataPtr, long dataLen) { this.setData(MemoryAddress.ofLong(dataPtr), dataLen); } public void setRowStride(int rowStride) { ImageDataNative.ROW_STRIDE_HANDLE.set(this.memory, rowStride); } public void setOffset(int x, int y) { ImageDataNative.OFFSET_HANDLE.set(this.memory, 0, x); ImageDataNative.OFFSET_HANDLE.set(this.memory, 1, y); } public void setExtent(int x, int y) { ImageDataNative.EXTENT_HANDLE.set(this.memory, 0, x); ImageDataNative.EXTENT_HANDLE.set(this.memory, 1, y); } public MemoryAddress getAddress() { return this.memory.address(); } @Override public void close() throws Exception { this.resourceScope.close(); } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/types/B4DIndexType.java ================================================ package graphics.kiln.blaze4d.core.types; public enum B4DIndexType { UINT16(0), UINT32(1); private final int value; B4DIndexType(int value) { this.value = value; } public int getValue() { return this.value; } public static B4DIndexType fromValue(int value) { switch (value) { case 0 -> { return B4DIndexType.UINT16; } case 1 -> { return B4DIndexType.UINT32; } default -> throw new RuntimeException("Invalid index type value " + value); } } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/types/B4DMeshData.java ================================================ package graphics.kiln.blaze4d.core.types; import graphics.kiln.blaze4d.core.natives.MeshDataNative; import jdk.incubator.foreign.MemoryAddress; import jdk.incubator.foreign.MemorySegment; import jdk.incubator.foreign.ResourceScope; /** * Wrapper class around the native CMeshData type. */ public class B4DMeshData implements AutoCloseable { private final ResourceScope resourceScope; private final MemorySegment memory; /** * Allocates a new mesh data instance with native backing memory. * All data will be uninitialized. */ public B4DMeshData() { this.resourceScope = ResourceScope.newSharedScope(); this.memory = MemorySegment.allocateNative(MeshDataNative.LAYOUT, this.resourceScope); } private void setVertexDataMem(MemoryAddress data, long dataLen) { MeshDataNative.VERTEX_DATA_PTR_HANDLE.set(this.memory, data); MeshDataNative.VERTEX_DATA_LEN_HANDLE.set(this.memory, dataLen); } public void setVertexData(long dataPtr, long dataLen) { this.setVertexDataMem(MemoryAddress.ofLong(dataPtr), dataLen); } public MemoryAddress getVertexDataPtr() { return (MemoryAddress) MeshDataNative.VERTEX_DATA_PTR_HANDLE.get(this.memory); } public long getVertexDataLen() { return (long) MeshDataNative.VERTEX_DATA_LEN_HANDLE.get(this.memory); } public void setIndexData() { MeshDataNative.INDEX_DATA_PTR_HANDLE.set(this.memory, MemoryAddress.ofLong(0L)); } private void setIndexDataMem(MemoryAddress data, long dataLen) { MeshDataNative.INDEX_DATA_PTR_HANDLE.set(this.memory, data); MeshDataNative.INDEX_DATA_LEN_HANDLE.set(this.memory, dataLen); } public void setIndexData(long dataPtr, long dataLen) { this.setIndexDataMem(MemoryAddress.ofLong(dataPtr), dataLen); } public MemoryAddress getIndexDataPtr() { return (MemoryAddress) MeshDataNative.INDEX_DATA_PTR_HANDLE.get(this.memory); } public long getIndexDataLen() { return (long) MeshDataNative.INDEX_DATA_LEN_HANDLE.get(this.memory); } public void setVertexStride(int vertexStride) { MeshDataNative.VERTEX_STRIDE_HANDLE.set(this.memory, vertexStride); } public int getVertexStride() { return (int) MeshDataNative.VERTEX_STRIDE_HANDLE.get(this.memory); } public void setIndexCount(int indexCount) { MeshDataNative.INDEX_COUNT_HANDLE.set(this.memory, indexCount); } public int getIndexCount() { return (int) MeshDataNative.INDEX_COUNT_HANDLE.get(this.memory); } public void setIndexType(B4DIndexType type) { MeshDataNative.INDEX_TYPE_HANDLE.set(this.memory, type.getValue()); } public B4DIndexType getIndexType() { return B4DIndexType.fromValue((int) MeshDataNative.INDEX_TYPE_HANDLE.get(this.memory)); } public void setIndexTypeRaw(int indexType) { MeshDataNative.INDEX_TYPE_HANDLE.set(this.memory, indexType); } public int getIndexTypeRaw() { return (int) MeshDataNative.INDEX_TYPE_HANDLE.get(this.memory); } public void setPrimitiveTopology(B4DPrimitiveTopology primitiveTopology) { MeshDataNative.PRIMITIVE_TOPOLOGY_HANDLE.set(this.memory, primitiveTopology.getValue()); } public B4DPrimitiveTopology getPrimitiveTopology() { return B4DPrimitiveTopology.fromRaw((int) MeshDataNative.PRIMITIVE_TOPOLOGY_HANDLE.get(this.memory)); } public void setPrimitiveTopologyRaw(int primitiveTopology) { MeshDataNative.PRIMITIVE_TOPOLOGY_HANDLE.set(this.memory, primitiveTopology); } public int getPrimitiveTopologyRaw() { return (int) MeshDataNative.PRIMITIVE_TOPOLOGY_HANDLE.get(this.memory); } public MemoryAddress getAddress() { return this.memory.address(); } @Override public void close() throws Exception { this.resourceScope.close(); } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/types/B4DPrimitiveTopology.java ================================================ package graphics.kiln.blaze4d.core.types; public enum B4DPrimitiveTopology { POINT_LIST(0), LINE_LIST(1), LINE_STRIP(2), TRIANGLE_LIST(3), TRIANGLE_STRIP(4), TRIANGLE_FAN(5); private final int value; B4DPrimitiveTopology(int value) { this.value = value; } public int getValue() { return this.value; } public static B4DPrimitiveTopology fromRaw(int value) { switch (value) { case 0 -> { return B4DPrimitiveTopology.POINT_LIST; } case 1 -> { return B4DPrimitiveTopology.LINE_LIST; } case 2 -> { return B4DPrimitiveTopology.LINE_STRIP; } case 3 -> { return B4DPrimitiveTopology.TRIANGLE_LIST; } case 4 -> { return B4DPrimitiveTopology.TRIANGLE_STRIP; } case 5 -> { return B4DPrimitiveTopology.TRIANGLE_FAN; } default -> throw new RuntimeException("Invalid primitive topology value " + value); } } public static B4DPrimitiveTopology fromGLMode(int mode) { switch (mode) { case 0 -> { return B4DPrimitiveTopology.POINT_LIST; } case 1 -> { return B4DPrimitiveTopology.LINE_LIST; } case 3 -> { return B4DPrimitiveTopology.LINE_STRIP; } case 4 -> { return B4DPrimitiveTopology.TRIANGLE_LIST; } case 5 -> { return B4DPrimitiveTopology.TRIANGLE_STRIP; } case 6 -> { return B4DPrimitiveTopology.TRIANGLE_FAN; } default -> throw new RuntimeException("Unsupported OpenGL mode " + mode); } } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/types/B4DUniform.java ================================================ package graphics.kiln.blaze4d.core.types; public enum B4DUniform { MODEL_VIEW_MATRIX(1L), PROJECTION_MATRIX(1L << 1), INVERSE_VIEW_ROTATION_MATRIX(1L << 2), TEXTURE_MATRIX(1L << 3), SCREEN_SIZE(1L << 4), COLOR_MODULATOR(1L << 5), LIGHT0_DIRECTION(1L << 6), LIGHT1_DIRECTION(1L << 7), FOG_START(1L << 8), FOG_END(1L << 9), FOG_COLOR(1L << 10), FOG_SHAPE(1L << 11), LINE_WIDTH(1L << 12), GAME_TIME(1L << 13), CHUNK_OFFSET(1L << 14); private final long value; B4DUniform(long value) { this.value = value; } public long getValue() { return this.value; } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/types/B4DUniformData.java ================================================ package graphics.kiln.blaze4d.core.types; import graphics.kiln.blaze4d.core.natives.McUniformDataNative; import jdk.incubator.foreign.MemoryAddress; import jdk.incubator.foreign.MemorySegment; import jdk.incubator.foreign.ResourceScope; public class B4DUniformData implements AutoCloseable { private final ResourceScope resourceScope; private final MemorySegment memory; public B4DUniformData() { this.resourceScope = ResourceScope.newSharedScope(); this.memory = MemorySegment.allocateNative(McUniformDataNative.LAYOUT, this.resourceScope); } public void setModelViewMatrix(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33) { McUniformDataNative.UNIFORM_HANDLE.set(this.memory, B4DUniform.MODEL_VIEW_MATRIX.getValue()); this.setMat4f32(m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33); } public void setProjectionMatrix(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33) { McUniformDataNative.UNIFORM_HANDLE.set(this.memory, B4DUniform.PROJECTION_MATRIX.getValue()); this.setMat4f32(m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33); } public void setChunkOffset(float x, float y, float z) { McUniformDataNative.UNIFORM_HANDLE.set(this.memory, B4DUniform.CHUNK_OFFSET.getValue()); this.setVec3f32(x, y, z); } public MemoryAddress getAddress() { return this.memory.address(); } @Override public void close() throws Exception { this.resourceScope.close(); } private void setVec3f32(float x, float y, float z) { McUniformDataNative.PAYLOAD_VEC3F32_HANDLE.set(this.memory, 0, x); McUniformDataNative.PAYLOAD_VEC3F32_HANDLE.set(this.memory, 1, y); McUniformDataNative.PAYLOAD_VEC3F32_HANDLE.set(this.memory, 2, z); } private void setMat4f32(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33) { McUniformDataNative.PAYLOAD_MAT4F32_HANDLE.set(this.memory, 0, m00); McUniformDataNative.PAYLOAD_MAT4F32_HANDLE.set(this.memory, 1, m10); McUniformDataNative.PAYLOAD_MAT4F32_HANDLE.set(this.memory, 2, m20); McUniformDataNative.PAYLOAD_MAT4F32_HANDLE.set(this.memory, 3, m30); McUniformDataNative.PAYLOAD_MAT4F32_HANDLE.set(this.memory, 4, m01); McUniformDataNative.PAYLOAD_MAT4F32_HANDLE.set(this.memory, 5, m11); McUniformDataNative.PAYLOAD_MAT4F32_HANDLE.set(this.memory, 6, m21); McUniformDataNative.PAYLOAD_MAT4F32_HANDLE.set(this.memory, 7, m31); McUniformDataNative.PAYLOAD_MAT4F32_HANDLE.set(this.memory, 8, m02); McUniformDataNative.PAYLOAD_MAT4F32_HANDLE.set(this.memory, 9, m12); McUniformDataNative.PAYLOAD_MAT4F32_HANDLE.set(this.memory, 10, m22); McUniformDataNative.PAYLOAD_MAT4F32_HANDLE.set(this.memory, 11, m32); McUniformDataNative.PAYLOAD_MAT4F32_HANDLE.set(this.memory, 12, m03); McUniformDataNative.PAYLOAD_MAT4F32_HANDLE.set(this.memory, 13, m13); McUniformDataNative.PAYLOAD_MAT4F32_HANDLE.set(this.memory, 14, m23); McUniformDataNative.PAYLOAD_MAT4F32_HANDLE.set(this.memory, 15, m33); } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/types/B4DVertexFormat.java ================================================ package graphics.kiln.blaze4d.core.types; import graphics.kiln.blaze4d.core.natives.VertexFormatNative; import jdk.incubator.foreign.MemoryAddress; import jdk.incubator.foreign.MemorySegment; import jdk.incubator.foreign.ResourceScope; import java.util.Optional; public class B4DVertexFormat implements AutoCloseable { private final ResourceScope resourceScope; private final MemorySegment memory; public B4DVertexFormat() { this.resourceScope = ResourceScope.newSharedScope(); this.memory = MemorySegment.allocateNative(VertexFormatNative.LAYOUT, this.resourceScope); } public void initialize() { this.setStride(0); this.setPosition(0, B4DFormat.UNDEFINED); this.setNormal(); this.setColor(); this.setUV0(); this.setUV1(); this.setUV2(); } public void setStride(int stride) { VertexFormatNative.STRIDE_HANDLE.set(this.memory, stride); } public void setPosition(FormatEntry entry) { this.setPosition(entry.offset, entry.format); } public void setPosition(int offset, B4DFormat format) { this.setPosition(offset, format.getValue()); } public void setPosition(int offset, int format) { VertexFormatNative.POSITION_OFFSET_HANDLE.set(this.memory, offset); VertexFormatNative.POSITION_FORMAT_HANDLE.set(this.memory, format); } public FormatEntry getPosition() { int offset = (int) VertexFormatNative.POSITION_OFFSET_HANDLE.get(this.memory); int format = (int) VertexFormatNative.POSITION_FORMAT_HANDLE.get(this.memory); return new FormatEntry(offset, B4DFormat.fromRaw(format)); } public void setNormal() { VertexFormatNative.HAS_NORMAL_HANDLE.set(this.memory, false); } public void setNormal(FormatEntry entry) { this.setNormal(entry.offset, entry.format); } public void setNormal(int offset, B4DFormat format) { this.setNormal(offset, format.getValue()); } public void setNormal(int offset, int format) { VertexFormatNative.NORMAL_OFFSET_HANDLE.set(this.memory, offset); VertexFormatNative.NORMAL_FORMAT_HANDLE.set(this.memory, format); VertexFormatNative.HAS_NORMAL_HANDLE.set(this.memory, true); } public Optional getNormal() { if ((boolean) VertexFormatNative.HAS_NORMAL_HANDLE.get(this.memory)) { int offset = (int) VertexFormatNative.NORMAL_OFFSET_HANDLE.get(this.memory); int format = (int) VertexFormatNative.NORMAL_FORMAT_HANDLE.get(this.memory); return Optional.of(new FormatEntry(offset, B4DFormat.fromRaw(format))); } return Optional.empty(); } public void setColor() { VertexFormatNative.HAS_COLOR_HANDLE.set(this.memory, false); } public void setColor(FormatEntry entry) { this.setColor(entry.offset, entry.format); } public void setColor(int offset, B4DFormat format) { this.setColor(offset, format.getValue()); } public void setColor(int offset, int format) { VertexFormatNative.COLOR_OFFSET_HANDLE.set(this.memory, offset); VertexFormatNative.COLOR_FORMAT_HANDLE.set(this.memory, format); VertexFormatNative.HAS_COLOR_HANDLE.set(this.memory, true); } public Optional getColor() { if ((boolean) VertexFormatNative.HAS_COLOR_HANDLE.get(this.memory)) { int offset = (int) VertexFormatNative.COLOR_OFFSET_HANDLE.get(this.memory); int format = (int) VertexFormatNative.COLOR_FORMAT_HANDLE.get(this.memory); return Optional.of(new FormatEntry(offset, B4DFormat.fromRaw(format))); } return Optional.empty(); } public void setUV0() { VertexFormatNative.HAS_UV0_HANDLE.set(this.memory, false); } public void setUV0(FormatEntry entry) { this.setUV0(entry.offset, entry.format); } public void setUV0(int offset, B4DFormat format) { this.setUV0(offset, format.getValue()); } public void setUV0(int offset, int format) { VertexFormatNative.UV0_OFFSET_HANDLE.set(this.memory, offset); VertexFormatNative.UV0_FORMAT_HANDLE.set(this.memory, format); VertexFormatNative.HAS_UV0_HANDLE.set(this.memory, true); } public Optional getUV0() { if ((boolean) VertexFormatNative.HAS_UV0_HANDLE.get(this.memory)) { int offset = (int) VertexFormatNative.UV0_OFFSET_HANDLE.get(this.memory); int format = (int) VertexFormatNative.UV0_FORMAT_HANDLE.get(this.memory); return Optional.of(new FormatEntry(offset, B4DFormat.fromRaw(format))); } return Optional.empty(); } public void setUV1() { VertexFormatNative.HAS_UV1_HANDLE.set(this.memory, false); } public void setUV1(FormatEntry entry) { this.setUV1(entry.offset, entry.format); } public void setUV1(int offset, B4DFormat format) { this.setUV1(offset, format.getValue()); } public void setUV1(int offset, int format) { VertexFormatNative.UV1_OFFSET_HANDLE.set(this.memory, offset); VertexFormatNative.UV1_FORMAT_HANDLE.set(this.memory, format); VertexFormatNative.HAS_UV1_HANDLE.set(this.memory, true); } public Optional getUV1() { if ((boolean) VertexFormatNative.HAS_UV1_HANDLE.get(this.memory)) { int offset = (int) VertexFormatNative.UV1_OFFSET_HANDLE.get(this.memory); int format = (int) VertexFormatNative.UV1_FORMAT_HANDLE.get(this.memory); return Optional.of(new FormatEntry(offset, B4DFormat.fromRaw(format))); } return Optional.empty(); } public void setUV2() { VertexFormatNative.HAS_UV2_HANDLE.set(this.memory, false); } public void setUV2(FormatEntry entry) { this.setUV2(entry.offset, entry.format); } public void setUV2(int offset, B4DFormat format) { this.setUV2(offset, format.getValue()); } public void setUV2(int offset, int format) { VertexFormatNative.UV2_OFFSET_HANDLE.set(this.memory, offset); VertexFormatNative.UV2_FORMAT_HANDLE.set(this.memory, format); VertexFormatNative.HAS_UV2_HANDLE.set(this.memory, true); } public Optional getUV2() { if ((boolean) VertexFormatNative.HAS_UV2_HANDLE.get(this.memory)) { int offset = (int) VertexFormatNative.UV2_OFFSET_HANDLE.get(this.memory); int format = (int) VertexFormatNative.UV2_FORMAT_HANDLE.get(this.memory); return Optional.of(new FormatEntry(offset, B4DFormat.fromRaw(format))); } return Optional.empty(); } public MemoryAddress getAddress() { return this.memory.address(); } @Override public void close() throws Exception { this.resourceScope.close(); } public record FormatEntry(int offset, B4DFormat format) { } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/types/BlendFactor.java ================================================ package graphics.kiln.blaze4d.core.types; public enum BlendFactor { ZERO(0), ONE(1), SRC_COLOR(2), ONE_MINUS_SRC_COLOR(3), DST_COLOR(4), ONE_MINUS_DST_COLOR(5), SRC_ALPHA(6), ONE_MINUS_SRC_ALPHA(7), DST_ALPHA(8), ONE_MINUS_DST_ALPHA(9); private final int value; BlendFactor(int value) { this.value = value; } public int getValue() { return this.value; } public static BlendFactor fromValue(int value) { return switch (value) { case 0 -> ZERO; case 1 -> ONE; case 2 -> SRC_COLOR; case 3 -> ONE_MINUS_SRC_COLOR; case 4 -> DST_COLOR; case 5 -> ONE_MINUS_DST_COLOR; case 6 -> SRC_ALPHA; case 7 -> ONE_MINUS_SRC_ALPHA; case 8 -> DST_ALPHA; case 9 -> ONE_MINUS_DST_ALPHA; default -> throw new IllegalArgumentException("Invalid blend factor value: " + value); }; } public static BlendFactor fromGlBlendFunc(int factor) { return switch (factor) { case 0 -> ZERO; case 1 -> ONE; case 0x0300 -> SRC_COLOR; case 0x0301 -> ONE_MINUS_SRC_COLOR; case 0x0302 -> SRC_ALPHA; case 0x0303 -> ONE_MINUS_SRC_ALPHA; case 0x0304 -> DST_ALPHA; case 0x0305 -> ONE_MINUS_DST_ALPHA; case 0x0306 -> DST_COLOR; case 0x0307 -> ONE_MINUS_DST_COLOR; default -> throw new IllegalArgumentException("Invalid blend func: " + factor); }; } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/types/BlendOp.java ================================================ package graphics.kiln.blaze4d.core.types; public enum BlendOp { ADD(0), SUBTRACT(1), REVERSE_SUBTRACT(2), MIN(3), MAX(4); private final int value; BlendOp(int value) { this.value = value; } public int getValue() { return this.value; } public static BlendOp fromValue(int value) { return switch (value) { case 0 -> ADD; case 1 -> SUBTRACT; case 2 -> REVERSE_SUBTRACT; case 3 -> MIN; case 4 -> MAX; default -> throw new IllegalArgumentException("Invalid blend op value: " + value); }; } public static BlendOp fromGlBlendEquation(int glEquation) { return switch (glEquation) { case 0x8006 -> ADD; case 0x8007 -> MIN; case 0x8008 -> MAX; case 0x800A -> SUBTRACT; case 0x800B -> REVERSE_SUBTRACT; default -> throw new IllegalArgumentException("Invalid blend equation: " + glEquation); }; } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/types/CompareOp.java ================================================ package graphics.kiln.blaze4d.core.types; public enum CompareOp { NEVER(0), LESS(1), EQUAL(2), LESS_OR_EQUAL(3), GREATER(4), NOT_EQUAL(5), GREATER_OR_EQUAL(6), ALWAYS(7); private final int value; CompareOp(int value) { this.value = value; } public int getValue() { return this.value; } public static CompareOp fromValue(int value) { return switch (value) { case 0 -> NEVER; case 1 -> LESS; case 2 -> EQUAL; case 3 -> LESS_OR_EQUAL; case 4 -> GREATER; case 5 -> NOT_EQUAL; case 6 -> GREATER_OR_EQUAL; case 7 -> ALWAYS; default -> throw new IllegalStateException("Invalid compare op value: " + value); }; } public static CompareOp fromGlDepthFunc(int glFunc) { return switch (glFunc) { case 0x0200 -> NEVER; case 0x0201 -> LESS; case 0x0202 -> EQUAL; case 0x0203 -> LESS_OR_EQUAL; case 0x0204 -> GREATER; case 0x0205 -> NOT_EQUAL; case 0x0206 -> GREATER_OR_EQUAL; case 0x0207 -> ALWAYS; default -> throw new IllegalStateException("Invalid depth function value: " + glFunc); }; } } ================================================ FILE: core/api/src/main/java/graphics/kiln/blaze4d/core/types/PipelineConfiguration.java ================================================ package graphics.kiln.blaze4d.core.types; import graphics.kiln.blaze4d.core.natives.PipelineConfigurationNative; import jdk.incubator.foreign.MemoryAddress; import jdk.incubator.foreign.MemorySegment; import jdk.incubator.foreign.ResourceScope; public class PipelineConfiguration implements AutoCloseable { private final ResourceScope resourceScope; private final MemorySegment memory; public PipelineConfiguration() { this.resourceScope = ResourceScope.newSharedScope(); this.memory = MemorySegment.allocateNative(PipelineConfigurationNative.LAYOUT, this.resourceScope); } public void setDepthTestEnable(boolean enable) { PipelineConfigurationNative.DEPTH_TEST_ENABLE_HANDLE.set(this.memory, enable ? 1 : 0); } public boolean getDepthTestEnable() { return ((int) PipelineConfigurationNative.DEPTH_TEST_ENABLE_HANDLE.get(this.memory)) != 0; } public void setDepthCompareOp(CompareOp op) { PipelineConfigurationNative.DEPTH_COMPARE_OP_HANDLE.set(this.memory, op.getValue()); } public CompareOp getDepthCompareOp() { return CompareOp.fromValue((int) PipelineConfigurationNative.DEPTH_COMPARE_OP_HANDLE.get(this.memory)); } public void setDepthWriteEnable(boolean enable) { PipelineConfigurationNative.DEPTH_WRITE_ENABLE_HANDLE.set(this.memory, enable ? 1 : 0); } public boolean getDepthWriteEnable() { return ((int) PipelineConfigurationNative.DEPTH_WRITE_ENABLE_HANDLE.get(this.memory)) != 0; } public void setBlendEnable(boolean enable) { PipelineConfigurationNative.BLEND_ENABLE_HANDLE.set(this.memory, enable ? 1 : 0); } public boolean getBlendEnable() { return ((int) PipelineConfigurationNative.BLEND_ENABLE_HANDLE.get(this.memory)) != 0; } public void setBlendColorOp(BlendOp op) { PipelineConfigurationNative.BLEND_COLOR_OP_HANDLE.set(this.memory, op.getValue()); } public BlendOp getBlendColorOp() { return BlendOp.fromValue((int) PipelineConfigurationNative.BLEND_COLOR_OP_HANDLE.get(this.memory)); } public void setBlendColorSrcFactor(BlendFactor factor) { PipelineConfigurationNative.BLEND_COLOR_SRC_FACTOR_HANDLE.set(this.memory, factor.getValue()); } public BlendFactor getBlendColorSrcFactor() { return BlendFactor.fromValue((int) PipelineConfigurationNative.BLEND_COLOR_SRC_FACTOR_HANDLE.get(this.memory)); } public void setBlendColorDstFactor(BlendFactor factor) { PipelineConfigurationNative.BLEND_COLOR_DST_FACTOR_HANDLE.set(this.memory, factor.getValue()); } public BlendFactor getBlendColorDstFactor() { return BlendFactor.fromValue((int) PipelineConfigurationNative.BLEND_COLOR_DST_FACTOR_HANDLE.get(this.memory)); } public void setBlendAlphaOp(BlendOp op) { PipelineConfigurationNative.BLEND_ALPHA_OP_HANDLE.set(this.memory, op.getValue()); } public BlendOp getBlendAlphaOp() { return BlendOp.fromValue((int) PipelineConfigurationNative.BLEND_ALPHA_OP_HANDLE.get(this.memory)); } public void setBlendAlphaSrcFactor(BlendFactor factor) { PipelineConfigurationNative.BLEND_ALPHA_SRC_FACTOR_HANDLE.set(this.memory, factor.getValue()); } public BlendFactor getBlendAlphaSrcFactor() { return BlendFactor.fromValue((int) PipelineConfigurationNative.BLEND_ALPHA_SRC_FACTOR_HANDLE.get(this.memory)); } public void setBlendAlphaDstFactor(BlendFactor factor) { PipelineConfigurationNative.BLEND_ALPHA_DST_FACTOR_HANDLE.set(this.memory, factor.getValue()); } public BlendFactor getBlendAlphaDstFactor() { return BlendFactor.fromValue((int) PipelineConfigurationNative.BLEND_ALPHA_DST_FACTOR_HANDLE.get(this.memory)); } public MemoryAddress getAddress() { return this.memory.address(); } @Override public void close() throws Exception { this.resourceScope.close(); } } ================================================ FILE: core/api/src/main/java/module-info.java ================================================ module graphics.kiln.blaze4d.core { requires jdk.incubator.foreign; requires com.google.gson; requires org.apache.logging.log4j; requires org.lwjgl.glfw; requires org.apache.commons.lang3; exports graphics.kiln.blaze4d.core; exports graphics.kiln.blaze4d.core.types; } ================================================ FILE: core/assets/.gitattributes ================================================ # # https://help.github.com/articles/dealing-with-line-endings/ # # These are explicitly windows files and should use crlf *.bat text eol=crlf ================================================ FILE: core/assets/.gitignore ================================================ # Gradle local files /build/ ================================================ FILE: core/assets/build.gradle.kts ================================================ apply() configure { shaders { targetSpriv(graphics.kiln.blaze4d.build.assets.shaders.SprivVersion.SPV_1_3) addProject("Emulator") { projectDir("emulator") addModule("debug/position.vert") addModule("debug/color.vert") addModule("debug/uv.vert") addModule("debug/null.vert") addModule("debug/debug.frag") addModule("debug/textured.frag") addModule("debug/background.vert") addModule("debug/background.frag") } addProject("Utils") { projectDir("utils") addModule("full_screen_quad.vert") addModule("blit.frag") } addProject("Debug") { projectDir("debug") addModule("apply.vert") addModule("apply.frag") addModule("font/msdf_font.vert") addModule("font/msdf_font.frag") addModule("basic.vert") addModule("basic.frag") } } } ================================================ FILE: core/assets/src/debug/apply.frag ================================================ #version 450 layout(input_attachment_index=0, set=0, binding=0) uniform subpassInput overlay; layout(location=0) out vec4 out_color; void main() { out_color = subpassLoad(overlay); //out_color = vec4(1.0, 1.0, 1.0, 1.0); } ================================================ FILE: core/assets/src/debug/apply.vert ================================================ #version 450 vec2 positions[4] = vec2[]( vec2(-1.0, 1.0), vec2(-1.0, -1.0), vec2(1.0, 1.0), vec2(1.0, -1.0) ); void main() { gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); } ================================================ FILE: core/assets/src/debug/basic.frag ================================================ #version 450 layout(location=0) in vec2 in_uv; layout(location=0) out vec4 color; void main() { color = vec4(in_uv, 0.0, 1.0); } ================================================ FILE: core/assets/src/debug/basic.vert ================================================ #version 450 layout(location=0) in vec3 position; layout(location=1) in vec2 in_uv; layout(location=0) out vec2 out_uv; layout(push_constant) uniform Constants { mat4 world_ndc; mat4 model_world; } pmat; void main() { gl_Position = pmat.world_ndc * (pmat.model_world * vec4(position, 1.0)); out_uv = in_uv; } ================================================ FILE: core/assets/src/debug/font/JetBrainsMono/OFL.txt ================================================ Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: https://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. ================================================ FILE: core/assets/src/debug/font/JetBrainsMono/tmp_built/regular.json ================================================ {"atlas":{"type":"msdf","distanceRange":5,"size":13,"width":512,"height":512,"yOrigin":"bottom"},"metrics":{"emSize":1,"lineHeight":1.3200000000000001,"ascender":1.02,"descender":-0.29999999999999999,"underlineY":-0.17999999999999999,"underlineThickness":0.050000000000000003},"glyphs":[{"unicode":32,"advance":0.59999999999999998},{"unicode":33,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230868,"bottom":-0.21442307692307683,"right":0.56923076923076932,"top":0.93942307692307692},"atlasBounds":{"left":504.5,"bottom":496.5,"right":511.5,"top":511.5}},{"unicode":34,"advance":0.59999999999999998,"planeBounds":{"left":-0.04615384615384608,"bottom":0.23384615384615393,"right":0.64615384615384619,"top":0.92615384615384622},"atlasBounds":{"left":502.5,"bottom":279.5,"right":511.5,"top":288.5}},{"unicode":35,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":70.5,"bottom":239.5,"right":82.5,"top":254.5}},{"unicode":36,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.36576923076923074,"right":0.72307692307692306,"top":1.0957692307692311},"atlasBounds":{"left":176.5,"bottom":440.5,"right":187.5,"top":459.5}},{"unicode":37,"advance":0.59999999999999998,"planeBounds":{"left":-0.1999999999999999,"bottom":-0.21192307692307685,"right":0.80000000000000004,"top":0.94192307692307697},"atlasBounds":{"left":83.5,"bottom":239.5,"right":96.5,"top":254.5}},{"unicode":38,"advance":0.59999999999999998,"planeBounds":{"left":-0.15103846153846145,"bottom":-0.2119230769230768,"right":0.77203846153846167,"top":0.94192307692307697},"atlasBounds":{"left":97.5,"bottom":239.5,"right":109.5,"top":254.5}},{"unicode":39,"advance":0.59999999999999998,"planeBounds":{"left":0.025269230769230839,"bottom":0.23384615384615393,"right":0.56373076923076926,"top":0.92615384615384622},"atlasBounds":{"left":504.5,"bottom":486.5,"right":511.5,"top":495.5}},{"unicode":40,"advance":0.59999999999999998,"planeBounds":{"left":-0.011153846153846082,"bottom":-0.33230769230769214,"right":0.68115384615384622,"top":1.0523076923076924},"atlasBounds":{"left":97.5,"bottom":419.5,"right":106.5,"top":437.5}},{"unicode":41,"advance":0.59999999999999998,"planeBounds":{"left":-0.081153846153846076,"bottom":-0.33230769230769214,"right":0.61115384615384616,"top":1.0523076923076924},"atlasBounds":{"left":107.5,"bottom":419.5,"right":116.5,"top":437.5}},{"unicode":42,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.10953846153846149,"right":0.76153846153846172,"top":0.81353846153846165},"atlasBounds":{"left":403.5,"bottom":101.5,"right":415.5,"top":113.5}},{"unicode":43,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.13153846153846144,"right":0.76153846153846172,"top":0.79153846153846164},"atlasBounds":{"left":416.5,"bottom":101.5,"right":428.5,"top":113.5}},{"unicode":44,"advance":0.59999999999999998,"planeBounds":{"left":-0.037692307692307615,"bottom":-0.38911538461538459,"right":0.57769230769230773,"top":0.38011538461538458},"atlasBounds":{"left":130.5,"bottom":76.5,"right":138.5,"top":86.5}},{"unicode":45,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384565,"bottom":0.060769230769230839,"right":0.68461538461538463,"top":0.59923076923076934},"atlasBounds":{"left":496.5,"bottom":464.5,"right":506.5,"top":471.5}},{"unicode":46,"advance":0.59999999999999998,"planeBounds":{"left":-0.0076923076923076155,"bottom":-0.23469230769230767,"right":0.60769230769230775,"top":0.38069230769230766},"atlasBounds":{"left":103.5,"bottom":56.5,"right":111.5,"top":64.5}},{"unicode":47,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.33230769230769225,"right":0.72307692307692306,"top":1.0523076923076924},"atlasBounds":{"left":117.5,"bottom":419.5,"right":128.5,"top":437.5}},{"unicode":48,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":121.5,"bottom":239.5,"right":132.5,"top":254.5}},{"unicode":49,"advance":0.59999999999999998,"planeBounds":{"left":-0.10807692307692299,"bottom":-0.2119230769230768,"right":0.73807692307692307,"top":0.94192307692307697},"atlasBounds":{"left":133.5,"bottom":239.5,"right":144.5,"top":254.5}},{"unicode":50,"advance":0.59999999999999998,"planeBounds":{"left":-0.12407692307692299,"bottom":-0.20692307692307685,"right":0.72207692307692306,"top":0.94692307692307698},"atlasBounds":{"left":145.5,"bottom":239.5,"right":156.5,"top":254.5}},{"unicode":51,"advance":0.59999999999999998,"planeBounds":{"left":-0.13307692307692301,"bottom":-0.21692307692307686,"right":0.71307692307692316,"top":0.93692307692307697},"atlasBounds":{"left":157.5,"bottom":239.5,"right":168.5,"top":254.5}},{"unicode":52,"advance":0.59999999999999998,"planeBounds":{"left":-0.14307692307692299,"bottom":-0.2119230769230768,"right":0.70307692307692315,"top":0.94192307692307697},"atlasBounds":{"left":169.5,"bottom":239.5,"right":180.5,"top":254.5}},{"unicode":53,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.21692307692307686,"right":0.72307692307692306,"top":0.93692307692307697},"atlasBounds":{"left":181.5,"bottom":239.5,"right":192.5,"top":254.5}},{"unicode":54,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.21692307692307686,"right":0.76153846153846172,"top":0.93692307692307697},"atlasBounds":{"left":193.5,"bottom":239.5,"right":205.5,"top":254.5}},{"unicode":55,"advance":0.59999999999999998,"planeBounds":{"left":-0.14903846153846143,"bottom":-0.2119230769230768,"right":0.77403846153846168,"top":0.94192307692307697},"atlasBounds":{"left":218.5,"bottom":239.5,"right":230.5,"top":254.5}},{"unicode":56,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2114230769230768,"right":0.76153846153846172,"top":0.94242307692307692},"atlasBounds":{"left":246.5,"bottom":239.5,"right":258.5,"top":254.5}},{"unicode":57,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.20692307692307685,"right":0.76153846153846172,"top":0.94692307692307698},"atlasBounds":{"left":259.5,"bottom":239.5,"right":271.5,"top":254.5}},{"unicode":58,"advance":0.59999999999999998,"planeBounds":{"left":-0.0076923076923076155,"bottom":-0.22499999999999989,"right":0.60769230769230775,"top":0.77500000000000002},"atlasBounds":{"left":175.5,"bottom":129.5,"right":183.5,"top":142.5}},{"unicode":59,"advance":0.59999999999999998,"planeBounds":{"left":-0.030192307692307602,"bottom":-0.37692307692307686,"right":0.58519230769230779,"top":0.77692307692307694},"atlasBounds":{"left":272.5,"bottom":239.5,"right":280.5,"top":254.5}},{"unicode":60,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.13153846153846147,"right":0.72307692307692306,"top":0.79153846153846164},"atlasBounds":{"left":429.5,"bottom":101.5,"right":440.5,"top":113.5}},{"unicode":61,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.054615384615384552,"right":0.72307692307692306,"top":0.71461538461538465},"atlasBounds":{"left":163.5,"bottom":76.5,"right":174.5,"top":86.5}},{"unicode":62,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.13153846153846147,"right":0.72307692307692306,"top":0.79153846153846164},"atlasBounds":{"left":441.5,"bottom":101.5,"right":452.5,"top":113.5}},{"unicode":63,"advance":0.59999999999999998,"planeBounds":{"left":-0.077115384615384558,"bottom":-0.21442307692307683,"right":0.69211538461538469,"top":0.93942307692307692},"atlasBounds":{"left":281.5,"bottom":239.5,"right":291.5,"top":254.5}},{"unicode":64,"advance":0.59999999999999998,"planeBounds":{"left":-0.15903846153846149,"bottom":-0.37384615384615372,"right":0.76403846153846167,"top":0.9338461538461541},"atlasBounds":{"left":286.5,"bottom":343.5,"right":298.5,"top":360.5}},{"unicode":65,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":292.5,"bottom":239.5,"right":304.5,"top":254.5}},{"unicode":66,"advance":0.59999999999999998,"planeBounds":{"left":-0.11157692307692298,"bottom":-0.2119230769230768,"right":0.73457692307692313,"top":0.94192307692307697},"atlasBounds":{"left":305.5,"bottom":239.5,"right":316.5,"top":254.5}},{"unicode":67,"advance":0.59999999999999998,"planeBounds":{"left":-0.12007692307692296,"bottom":-0.2119230769230768,"right":0.72607692307692306,"top":0.94192307692307697},"atlasBounds":{"left":329.5,"bottom":239.5,"right":340.5,"top":254.5}},{"unicode":68,"advance":0.59999999999999998,"planeBounds":{"left":-0.12107692307692298,"bottom":-0.2119230769230768,"right":0.72507692307692306,"top":0.94192307692307697},"atlasBounds":{"left":353.5,"bottom":239.5,"right":364.5,"top":254.5}},{"unicode":69,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692298,"bottom":-0.2119230769230768,"right":0.73307692307692307,"top":0.94192307692307697},"atlasBounds":{"left":377.5,"bottom":239.5,"right":388.5,"top":254.5}},{"unicode":70,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692295,"bottom":-0.2119230769230768,"right":0.73307692307692307,"top":0.94192307692307697},"atlasBounds":{"left":389.5,"bottom":239.5,"right":400.5,"top":254.5}},{"unicode":71,"advance":0.59999999999999998,"planeBounds":{"left":-0.12007692307692296,"bottom":-0.2119230769230768,"right":0.72607692307692306,"top":0.94192307692307697},"atlasBounds":{"left":453.5,"bottom":239.5,"right":464.5,"top":254.5}},{"unicode":72,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":479.5,"bottom":239.5,"right":490.5,"top":254.5}},{"unicode":73,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":491.5,"bottom":239.5,"right":502.5,"top":254.5}},{"unicode":74,"advance":0.59999999999999998,"planeBounds":{"left":-0.15307692307692297,"bottom":-0.21692307692307686,"right":0.69307692307692315,"top":0.93692307692307697},"atlasBounds":{"left":0.5,"bottom":222.5,"right":11.5,"top":237.5}},{"unicode":75,"advance":0.59999999999999998,"planeBounds":{"left":-0.13553846153846144,"bottom":-0.2119230769230768,"right":0.78753846153846163,"top":0.94192307692307697},"atlasBounds":{"left":12.5,"bottom":222.5,"right":24.5,"top":237.5}},{"unicode":76,"advance":0.59999999999999998,"planeBounds":{"left":-0.083076923076922979,"bottom":-0.2119230769230768,"right":0.7630769230769231,"top":0.94192307692307697},"atlasBounds":{"left":25.5,"bottom":222.5,"right":36.5,"top":237.5}},{"unicode":77,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":37.5,"bottom":222.5,"right":48.5,"top":237.5}},{"unicode":78,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":49.5,"bottom":222.5,"right":60.5,"top":237.5}},{"unicode":79,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":61.5,"bottom":222.5,"right":72.5,"top":237.5}},{"unicode":80,"advance":0.59999999999999998,"planeBounds":{"left":-0.10207692307692301,"bottom":-0.2119230769230768,"right":0.74407692307692308,"top":0.94192307692307697},"atlasBounds":{"left":86.5,"bottom":222.5,"right":97.5,"top":237.5}},{"unicode":81,"advance":0.59999999999999998,"planeBounds":{"left":-0.12007692307692296,"bottom":-0.37384615384615372,"right":0.72607692307692306,"top":0.9338461538461541},"atlasBounds":{"left":299.5,"bottom":343.5,"right":310.5,"top":360.5}},{"unicode":82,"advance":0.59999999999999998,"planeBounds":{"left":-0.10457692307692298,"bottom":-0.2119230769230768,"right":0.74157692307692313,"top":0.94192307692307697},"atlasBounds":{"left":98.5,"bottom":222.5,"right":109.5,"top":237.5}},{"unicode":83,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":110.5,"bottom":222.5,"right":121.5,"top":237.5}},{"unicode":84,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":122.5,"bottom":222.5,"right":134.5,"top":237.5}},{"unicode":85,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.21692307692307686,"right":0.72307692307692306,"top":0.93692307692307697},"atlasBounds":{"left":135.5,"bottom":222.5,"right":146.5,"top":237.5}},{"unicode":86,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":147.5,"bottom":222.5,"right":159.5,"top":237.5}},{"unicode":87,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999996,"bottom":-0.2119230769230768,"right":0.80000000000000004,"top":0.94192307692307697},"atlasBounds":{"left":160.5,"bottom":222.5,"right":173.5,"top":237.5}},{"unicode":88,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":174.5,"bottom":222.5,"right":186.5,"top":237.5}},{"unicode":89,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":200.5,"bottom":222.5,"right":212.5,"top":237.5}},{"unicode":90,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":213.5,"bottom":222.5,"right":224.5,"top":237.5}},{"unicode":91,"advance":0.59999999999999998,"planeBounds":{"left":-0.01865384615384607,"bottom":-0.33230769230769225,"right":0.67365384615384616,"top":1.0523076923076924},"atlasBounds":{"left":129.5,"bottom":419.5,"right":138.5,"top":437.5}},{"unicode":92,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.33230769230769225,"right":0.72307692307692306,"top":1.0523076923076924},"atlasBounds":{"left":139.5,"bottom":419.5,"right":150.5,"top":437.5}},{"unicode":93,"advance":0.59999999999999998,"planeBounds":{"left":-0.07365384615384607,"bottom":-0.33230769230769225,"right":0.61865384615384622,"top":1.0523076923076924},"atlasBounds":{"left":151.5,"bottom":419.5,"right":160.5,"top":437.5}},{"unicode":94,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":0.111923076923077,"right":0.72307692307692306,"top":0.95807692307692316},"atlasBounds":{"left":259.5,"bottom":88.5,"right":270.5,"top":99.5}},{"unicode":95,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":-0.29326923076923078,"right":0.76153846153846172,"top":0.16826923076923078},"atlasBounds":{"left":261.5,"bottom":48.5,"right":273.5,"top":54.5}},{"unicode":96,"advance":0.59999999999999998,"planeBounds":{"left":-0.040692307692307618,"bottom":0.44576923076923086,"right":0.57469230769230772,"top":0.98423076923076935},"atlasBounds":{"left":215.5,"bottom":47.5,"right":223.5,"top":54.5}},{"unicode":97,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.22499999999999989,"right":0.71057692307692311,"top":0.77500000000000002},"atlasBounds":{"left":378.5,"bottom":129.5,"right":389.5,"top":142.5}},{"unicode":98,"advance":0.59999999999999998,"planeBounds":{"left":-0.11957692307692301,"bottom":-0.21692307692307686,"right":0.72657692307692312,"top":0.93692307692307697},"atlasBounds":{"left":238.5,"bottom":222.5,"right":249.5,"top":237.5}},{"unicode":99,"advance":0.59999999999999998,"planeBounds":{"left":-0.12157692307692299,"bottom":-0.22499999999999989,"right":0.72457692307692312,"top":0.77500000000000002},"atlasBounds":{"left":402.5,"bottom":129.5,"right":413.5,"top":142.5}},{"unicode":100,"advance":0.59999999999999998,"planeBounds":{"left":-0.126576923076923,"bottom":-0.21692307692307686,"right":0.71957692307692311,"top":0.93692307692307697},"atlasBounds":{"left":250.5,"bottom":222.5,"right":261.5,"top":237.5}},{"unicode":101,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.22499999999999989,"right":0.72307692307692306,"top":0.77500000000000002},"atlasBounds":{"left":427.5,"bottom":129.5,"right":438.5,"top":142.5}},{"unicode":102,"advance":0.59999999999999998,"planeBounds":{"left":-0.16403846153846147,"bottom":-0.2119230769230768,"right":0.75903846153846166,"top":0.94192307692307697},"atlasBounds":{"left":262.5,"bottom":222.5,"right":274.5,"top":237.5}},{"unicode":103,"advance":0.59999999999999998,"planeBounds":{"left":-0.12557692307692297,"bottom":-0.38692307692307687,"right":0.72057692307692311,"top":0.76692307692307693},"atlasBounds":{"left":275.5,"bottom":222.5,"right":286.5,"top":237.5}},{"unicode":104,"advance":0.59999999999999998,"planeBounds":{"left":-0.12207692307692299,"bottom":-0.2119230769230768,"right":0.72407692307692306,"top":0.94192307692307697},"atlasBounds":{"left":287.5,"bottom":222.5,"right":298.5,"top":237.5}},{"unicode":105,"advance":0.59999999999999998,"planeBounds":{"left":-0.14153846153846145,"bottom":-0.2268846153846153,"right":0.78153846153846163,"top":1.0038846153846155},"atlasBounds":{"left":381.5,"bottom":307.5,"right":393.5,"top":323.5}},{"unicode":106,"advance":0.59999999999999998,"planeBounds":{"left":-0.16007692307692298,"bottom":-0.39380769230769219,"right":0.68607692307692314,"top":0.99080769230769239},"atlasBounds":{"left":161.5,"bottom":419.5,"right":172.5,"top":437.5}},{"unicode":107,"advance":0.59999999999999998,"planeBounds":{"left":-0.1355384615384615,"bottom":-0.2119230769230768,"right":0.78753846153846163,"top":0.94192307692307697},"atlasBounds":{"left":313.5,"bottom":222.5,"right":325.5,"top":237.5}},{"unicode":108,"advance":0.59999999999999998,"planeBounds":{"left":-0.17153846153846145,"bottom":-0.2119230769230768,"right":0.75153846153846171,"top":0.94192307692307697},"atlasBounds":{"left":326.5,"bottom":222.5,"right":338.5,"top":237.5}},{"unicode":109,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":-0.21999999999999989,"right":0.76153846153846172,"top":0.78000000000000003},"atlasBounds":{"left":0.5,"bottom":114.5,"right":12.5,"top":127.5}},{"unicode":110,"advance":0.59999999999999998,"planeBounds":{"left":-0.12207692307692299,"bottom":-0.21999999999999989,"right":0.72407692307692306,"top":0.78000000000000003},"atlasBounds":{"left":13.5,"bottom":114.5,"right":24.5,"top":127.5}},{"unicode":111,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.22499999999999995,"right":0.72307692307692306,"top":0.77500000000000002},"atlasBounds":{"left":25.5,"bottom":114.5,"right":36.5,"top":127.5}},{"unicode":112,"advance":0.59999999999999998,"planeBounds":{"left":-0.11857692307692301,"bottom":-0.38692307692307687,"right":0.72757692307692312,"top":0.76692307692307693},"atlasBounds":{"left":339.5,"bottom":222.5,"right":350.5,"top":237.5}},{"unicode":113,"advance":0.59999999999999998,"planeBounds":{"left":-0.12507692307692297,"bottom":-0.38692307692307687,"right":0.72107692307692306,"top":0.76692307692307693},"atlasBounds":{"left":351.5,"bottom":222.5,"right":362.5,"top":237.5}},{"unicode":114,"advance":0.59999999999999998,"planeBounds":{"left":-0.10157692307692301,"bottom":-0.21999999999999989,"right":0.74457692307692314,"top":0.78000000000000003},"atlasBounds":{"left":129.5,"bottom":114.5,"right":140.5,"top":127.5}},{"unicode":115,"advance":0.59999999999999998,"planeBounds":{"left":-0.11807692307692295,"bottom":-0.22499999999999995,"right":0.72807692307692307,"top":0.77500000000000002},"atlasBounds":{"left":141.5,"bottom":114.5,"right":152.5,"top":127.5}},{"unicode":116,"advance":0.59999999999999998,"planeBounds":{"left":-0.17403846153846145,"bottom":-0.22442307692307686,"right":0.74903846153846165,"top":0.92942307692307691},"atlasBounds":{"left":376.5,"bottom":222.5,"right":388.5,"top":237.5}},{"unicode":117,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2299999999999999,"right":0.72307692307692306,"top":0.77000000000000002},"atlasBounds":{"left":165.5,"bottom":114.5,"right":176.5,"top":127.5}},{"unicode":118,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.22499999999999995,"right":0.76153846153846172,"top":0.77500000000000002},"atlasBounds":{"left":177.5,"bottom":114.5,"right":189.5,"top":127.5}},{"unicode":119,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.22499999999999995,"right":0.76153846153846172,"top":0.77500000000000002},"atlasBounds":{"left":190.5,"bottom":114.5,"right":202.5,"top":127.5}},{"unicode":120,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.22499999999999995,"right":0.76153846153846172,"top":0.77500000000000002},"atlasBounds":{"left":217.5,"bottom":114.5,"right":229.5,"top":127.5}},{"unicode":121,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.39192307692307682,"right":0.76153846153846172,"top":0.76192307692307693},"atlasBounds":{"left":389.5,"bottom":222.5,"right":401.5,"top":237.5}},{"unicode":122,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22499999999999995,"right":0.72307692307692306,"top":0.77500000000000002},"atlasBounds":{"left":270.5,"bottom":114.5,"right":281.5,"top":127.5}},{"unicode":123,"advance":0.59999999999999998,"planeBounds":{"left":-0.13307692307692295,"bottom":-0.33230769230769225,"right":0.71307692307692316,"top":1.0523076923076924},"atlasBounds":{"left":173.5,"bottom":419.5,"right":184.5,"top":437.5}},{"unicode":124,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230823,"bottom":-0.33230769230769225,"right":0.56923076923076932,"top":1.0523076923076924},"atlasBounds":{"left":185.5,"bottom":419.5,"right":192.5,"top":437.5}},{"unicode":125,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692295,"bottom":-0.33230769230769225,"right":0.73307692307692307,"top":1.0523076923076924},"atlasBounds":{"left":193.5,"bottom":419.5,"right":204.5,"top":437.5}},{"unicode":126,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":0.044807692307692402,"right":0.72307692307692306,"top":0.66019230769230774},"atlasBounds":{"left":276.5,"bottom":56.5,"right":287.5,"top":64.5}},{"unicode":160,"advance":0.59999999999999998},{"unicode":161,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230868,"bottom":-0.38892307692307682,"right":0.56923076923076932,"top":0.76492307692307693},"atlasBounds":{"left":504.5,"bottom":345.5,"right":511.5,"top":360.5}},{"unicode":162,"advance":0.59999999999999998,"planeBounds":{"left":-0.12157692307692299,"bottom":-0.34038461538461529,"right":0.72457692307692312,"top":0.89038461538461544},"atlasBounds":{"left":419.5,"bottom":307.5,"right":430.5,"top":323.5}},{"unicode":163,"advance":0.59999999999999998,"planeBounds":{"left":-0.11807692307692301,"bottom":-0.20692307692307685,"right":0.72807692307692307,"top":0.94692307692307698},"atlasBounds":{"left":440.5,"bottom":222.5,"right":451.5,"top":237.5}},{"unicode":164,"advance":0.59999999999999998,"planeBounds":{"left":-0.16103846153846149,"bottom":-0.08753846153846144,"right":0.76203846153846166,"top":0.83553846153846167},"atlasBounds":{"left":33.5,"bottom":87.5,"right":45.5,"top":99.5}},{"unicode":165,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":452.5,"bottom":222.5,"right":464.5,"top":237.5}},{"unicode":166,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230823,"bottom":-0.33230769230769225,"right":0.56923076923076932,"top":1.0523076923076924},"atlasBounds":{"left":205.5,"bottom":419.5,"right":212.5,"top":437.5}},{"unicode":167,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.36084615384615376,"right":0.72307692307692306,"top":0.946846153846154},"atlasBounds":{"left":385.5,"bottom":343.5,"right":396.5,"top":360.5}},{"unicode":168,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384523,"bottom":0.44376923076923086,"right":0.68461538461538463,"top":0.98223076923076935},"atlasBounds":{"left":132.5,"bottom":47.5,"right":142.5,"top":54.5}},{"unicode":169,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.11346153846153834,"right":0.76153846153846172,"top":0.96346153846153848},"atlasBounds":{"left":127.5,"bottom":143.5,"right":139.5,"top":157.5}},{"unicode":170,"advance":0.59999999999999998,"planeBounds":{"left":-0.048653846153846068,"bottom":0.24884615384615397,"right":0.64365384615384624,"top":0.94115384615384623},"atlasBounds":{"left":130.5,"bottom":65.5,"right":139.5,"top":74.5}},{"unicode":171,"advance":0.59999999999999998,"planeBounds":{"left":-0.1999999999999999,"bottom":-0.18153846153846148,"right":0.80000000000000004,"top":0.7415384615384617},"atlasBounds":{"left":46.5,"bottom":87.5,"right":59.5,"top":99.5}},{"unicode":172,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":0.0023076923076923847,"right":0.72307692307692306,"top":0.61769230769230776},"atlasBounds":{"left":91.5,"bottom":56.5,"right":102.5,"top":64.5}},{"unicode":173,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384565,"bottom":0.060769230769230839,"right":0.68461538461538463,"top":0.59923076923076934},"atlasBounds":{"left":184.5,"bottom":47.5,"right":194.5,"top":54.5}},{"unicode":174,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.11346153846153834,"right":0.76153846153846172,"top":0.96346153846153848},"atlasBounds":{"left":140.5,"bottom":143.5,"right":152.5,"top":157.5}},{"unicode":175,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384565,"bottom":0.47673076923076929,"right":0.68461538461538463,"top":0.93826923076923086},"atlasBounds":{"left":250.5,"bottom":48.5,"right":260.5,"top":54.5}},{"unicode":176,"advance":0.59999999999999998,"planeBounds":{"left":-0.04615384615384608,"bottom":0.24884615384615397,"right":0.64615384615384619,"top":0.94115384615384623},"atlasBounds":{"left":53.5,"bottom":65.5,"right":62.5,"top":74.5}},{"unicode":177,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.21499999999999989,"right":0.76153846153846172,"top":0.78500000000000003},"atlasBounds":{"left":357.5,"bottom":114.5,"right":369.5,"top":127.5}},{"unicode":178,"advance":0.59999999999999998,"planeBounds":{"left":-0.043153846153846098,"bottom":0.19942307692307704,"right":0.64915384615384619,"top":1.0455769230769232},"atlasBounds":{"left":421.5,"bottom":88.5,"right":430.5,"top":99.5}},{"unicode":179,"advance":0.59999999999999998,"planeBounds":{"left":-0.062153846153846067,"bottom":0.18992307692307703,"right":0.63015384615384618,"top":1.0360769230769231},"atlasBounds":{"left":431.5,"bottom":88.5,"right":440.5,"top":99.5}},{"unicode":180,"advance":0.59999999999999998,"planeBounds":{"left":0.025807692307692406,"bottom":0.44576923076923086,"right":0.64119230769230773,"top":0.98423076923076935},"atlasBounds":{"left":33.5,"bottom":47.5,"right":41.5,"top":54.5}},{"unicode":181,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.39192307692307682,"right":0.72307692307692306,"top":0.76192307692307693},"atlasBounds":{"left":465.5,"bottom":222.5,"right":476.5,"top":237.5}},{"unicode":182,"advance":0.59999999999999998,"planeBounds":{"left":-0.13807692307692301,"bottom":-0.37884615384615383,"right":0.70807692307692316,"top":0.92884615384615399},"atlasBounds":{"left":397.5,"bottom":343.5,"right":408.5,"top":360.5}},{"unicode":183,"advance":0.59999999999999998,"planeBounds":{"left":-0.0076923076923076155,"bottom":0.035307692307692387,"right":0.60769230769230775,"top":0.65069230769230779},"atlasBounds":{"left":23.5,"bottom":56.5,"right":31.5,"top":64.5}},{"unicode":184,"advance":0.59999999999999998,"planeBounds":{"left":-0.00019230769230760104,"bottom":-0.39619230769230768,"right":0.6151923076923077,"top":0.21919230769230769},"atlasBounds":{"left":14.5,"bottom":56.5,"right":22.5,"top":64.5}},{"unicode":185,"advance":0.59999999999999998,"planeBounds":{"left":-0.038653846153846039,"bottom":0.194423076923077,"right":0.65365384615384625,"top":1.0405769230769231},"atlasBounds":{"left":441.5,"bottom":88.5,"right":450.5,"top":99.5}},{"unicode":186,"advance":0.59999999999999998,"planeBounds":{"left":-0.0076923076923075939,"bottom":0.24384615384615396,"right":0.60769230769230775,"top":0.93615384615384623},"atlasBounds":{"left":441.5,"bottom":77.5,"right":449.5,"top":86.5}},{"unicode":187,"advance":0.59999999999999998,"planeBounds":{"left":-0.1999999999999999,"bottom":-0.18153846153846148,"right":0.80000000000000004,"top":0.7415384615384617},"atlasBounds":{"left":75.5,"bottom":87.5,"right":88.5,"top":99.5}},{"unicode":188,"advance":0.59999999999999998,"planeBounds":{"left":-0.17653846153846148,"bottom":-0.2119230769230768,"right":0.74653846153846171,"top":0.94192307692307697},"atlasBounds":{"left":489.5,"bottom":222.5,"right":501.5,"top":237.5}},{"unicode":189,"advance":0.59999999999999998,"planeBounds":{"left":-0.1999999999999999,"bottom":-0.2119230769230768,"right":0.80000000000000004,"top":0.94192307692307697},"atlasBounds":{"left":0.5,"bottom":206.5,"right":13.5,"top":221.5}},{"unicode":190,"advance":0.59999999999999998,"planeBounds":{"left":-0.17853846153846151,"bottom":-0.2119230769230768,"right":0.7445384615384617,"top":0.94192307692307697},"atlasBounds":{"left":14.5,"bottom":206.5,"right":26.5,"top":221.5}},{"unicode":191,"advance":0.59999999999999998,"planeBounds":{"left":-0.097115384615384562,"bottom":-0.38442307692307687,"right":0.67211538461538467,"top":0.76942307692307688},"atlasBounds":{"left":27.5,"bottom":206.5,"right":37.5,"top":221.5}},{"unicode":192,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.21730769230769215,"right":0.76153846153846172,"top":1.1673076923076924},"atlasBounds":{"left":213.5,"bottom":419.5,"right":225.5,"top":437.5}},{"unicode":193,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.21730769230769215,"right":0.76153846153846172,"top":1.1673076923076924},"atlasBounds":{"left":226.5,"bottom":419.5,"right":238.5,"top":437.5}},{"unicode":194,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.21730769230769215,"right":0.76153846153846172,"top":1.1673076923076924},"atlasBounds":{"left":239.5,"bottom":419.5,"right":251.5,"top":437.5}},{"unicode":195,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.21980769230769215,"right":0.76153846153846172,"top":1.1648076923076924},"atlasBounds":{"left":252.5,"bottom":419.5,"right":264.5,"top":437.5}},{"unicode":196,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.21980769230769215,"right":0.76153846153846172,"top":1.1648076923076924},"atlasBounds":{"left":265.5,"bottom":419.5,"right":277.5,"top":437.5}},{"unicode":197,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.19530769230769215,"right":0.76153846153846172,"top":1.1893076923076924},"atlasBounds":{"left":278.5,"bottom":419.5,"right":290.5,"top":437.5}},{"unicode":198,"advance":0.59999999999999998,"planeBounds":{"left":-0.1999999999999999,"bottom":-0.2119230769230768,"right":0.80000000000000004,"top":0.94192307692307697},"atlasBounds":{"left":90.5,"bottom":206.5,"right":103.5,"top":221.5}},{"unicode":199,"advance":0.59999999999999998,"planeBounds":{"left":-0.12007692307692296,"bottom":-0.42380769230769216,"right":0.72607692307692306,"top":0.96080769230769247},"atlasBounds":{"left":291.5,"bottom":419.5,"right":302.5,"top":437.5}},{"unicode":200,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692298,"bottom":-0.21730769230769215,"right":0.73307692307692307,"top":1.1673076923076924},"atlasBounds":{"left":303.5,"bottom":419.5,"right":314.5,"top":437.5}},{"unicode":201,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692298,"bottom":-0.21730769230769215,"right":0.73307692307692307,"top":1.1673076923076924},"atlasBounds":{"left":315.5,"bottom":419.5,"right":326.5,"top":437.5}},{"unicode":202,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692298,"bottom":-0.21730769230769215,"right":0.73307692307692307,"top":1.1673076923076924},"atlasBounds":{"left":327.5,"bottom":419.5,"right":338.5,"top":437.5}},{"unicode":203,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692298,"bottom":-0.21980769230769215,"right":0.73307692307692307,"top":1.1648076923076924},"atlasBounds":{"left":339.5,"bottom":419.5,"right":350.5,"top":437.5}},{"unicode":204,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.21730769230769215,"right":0.72307692307692306,"top":1.1673076923076924},"atlasBounds":{"left":351.5,"bottom":419.5,"right":362.5,"top":437.5}},{"unicode":205,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.21730769230769215,"right":0.72307692307692306,"top":1.1673076923076924},"atlasBounds":{"left":377.5,"bottom":419.5,"right":388.5,"top":437.5}},{"unicode":206,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.21730769230769215,"right":0.72307692307692306,"top":1.1673076923076924},"atlasBounds":{"left":389.5,"bottom":419.5,"right":400.5,"top":437.5}},{"unicode":207,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.21980769230769215,"right":0.72307692307692306,"top":1.1648076923076924},"atlasBounds":{"left":401.5,"bottom":419.5,"right":412.5,"top":437.5}},{"unicode":208,"advance":0.59999999999999998,"planeBounds":{"left":-0.20053846153846144,"bottom":-0.2119230769230768,"right":0.72253846153846168,"top":0.94192307692307697},"atlasBounds":{"left":242.5,"bottom":206.5,"right":254.5,"top":221.5}},{"unicode":209,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.21980769230769215,"right":0.72307692307692306,"top":1.1648076923076924},"atlasBounds":{"left":413.5,"bottom":419.5,"right":424.5,"top":437.5}},{"unicode":210,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.22230769230769215,"right":0.72307692307692306,"top":1.1623076923076925},"atlasBounds":{"left":425.5,"bottom":419.5,"right":436.5,"top":437.5}},{"unicode":211,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.22230769230769215,"right":0.72307692307692306,"top":1.1623076923076925},"atlasBounds":{"left":437.5,"bottom":419.5,"right":448.5,"top":437.5}},{"unicode":212,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.22230769230769215,"right":0.72307692307692306,"top":1.1623076923076925},"atlasBounds":{"left":449.5,"bottom":419.5,"right":460.5,"top":437.5}},{"unicode":213,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.22480769230769221,"right":0.72307692307692306,"top":1.1598076923076923},"atlasBounds":{"left":461.5,"bottom":419.5,"right":472.5,"top":437.5}},{"unicode":214,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.22480769230769221,"right":0.72307692307692306,"top":1.1598076923076923},"atlasBounds":{"left":473.5,"bottom":419.5,"right":484.5,"top":437.5}},{"unicode":215,"advance":0.59999999999999998,"planeBounds":{"left":-0.12357692307692301,"bottom":-0.092076923076923015,"right":0.72257692307692312,"top":0.75407692307692309},"atlasBounds":{"left":60.5,"bottom":75.5,"right":71.5,"top":86.5}},{"unicode":216,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.26038461538461533,"right":0.76153846153846172,"top":0.97038461538461551},"atlasBounds":{"left":483.5,"bottom":307.5,"right":495.5,"top":323.5}},{"unicode":217,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22230769230769215,"right":0.72307692307692306,"top":1.1623076923076925},"atlasBounds":{"left":46.5,"bottom":399.5,"right":57.5,"top":417.5}},{"unicode":218,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22230769230769215,"right":0.72307692307692306,"top":1.1623076923076925},"atlasBounds":{"left":58.5,"bottom":399.5,"right":69.5,"top":417.5}},{"unicode":219,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22230769230769215,"right":0.72307692307692306,"top":1.1623076923076925},"atlasBounds":{"left":70.5,"bottom":399.5,"right":81.5,"top":417.5}},{"unicode":220,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22480769230769221,"right":0.72307692307692306,"top":1.1598076923076923},"atlasBounds":{"left":82.5,"bottom":399.5,"right":93.5,"top":417.5}},{"unicode":221,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.21730769230769215,"right":0.76153846153846172,"top":1.1673076923076924},"atlasBounds":{"left":94.5,"bottom":399.5,"right":106.5,"top":417.5}},{"unicode":222,"advance":0.59999999999999998,"planeBounds":{"left":-0.10307692307692301,"bottom":-0.2119230769230768,"right":0.74307692307692308,"top":0.94192307692307697},"atlasBounds":{"left":174.5,"bottom":190.5,"right":185.5,"top":205.5}},{"unicode":223,"advance":0.59999999999999998,"planeBounds":{"left":-0.11207692307692298,"bottom":-0.20692307692307685,"right":0.73407692307692307,"top":0.94692307692307698},"atlasBounds":{"left":186.5,"bottom":190.5,"right":197.5,"top":205.5}},{"unicode":224,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.2278846153846153,"right":0.71057692307692311,"top":1.0028846153846156},"atlasBounds":{"left":12.5,"bottom":289.5,"right":23.5,"top":305.5}},{"unicode":225,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.2278846153846153,"right":0.71057692307692311,"top":1.0028846153846156},"atlasBounds":{"left":24.5,"bottom":289.5,"right":35.5,"top":305.5}},{"unicode":226,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.2278846153846153,"right":0.71057692307692311,"top":1.0028846153846156},"atlasBounds":{"left":36.5,"bottom":289.5,"right":47.5,"top":305.5}},{"unicode":227,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.2303846153846153,"right":0.71057692307692311,"top":1.0003846153846154},"atlasBounds":{"left":48.5,"bottom":289.5,"right":59.5,"top":305.5}},{"unicode":228,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.23188461538461524,"right":0.71057692307692311,"top":0.99888461538461559},"atlasBounds":{"left":60.5,"bottom":289.5,"right":71.5,"top":305.5}},{"unicode":229,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.2023846153846153,"right":0.71057692307692311,"top":1.0283846153846155},"atlasBounds":{"left":72.5,"bottom":289.5,"right":83.5,"top":305.5}},{"unicode":230,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.22499999999999989,"right":0.76153846153846172,"top":0.77500000000000002},"atlasBounds":{"left":212.5,"bottom":100.5,"right":224.5,"top":113.5}},{"unicode":231,"advance":0.59999999999999998,"planeBounds":{"left":-0.12157692307692299,"bottom":-0.39842307692307688,"right":0.72457692307692312,"top":0.75542307692307697},"atlasBounds":{"left":262.5,"bottom":190.5,"right":273.5,"top":205.5}},{"unicode":232,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.2278846153846153,"right":0.72307692307692306,"top":1.0028846153846156},"atlasBounds":{"left":84.5,"bottom":289.5,"right":95.5,"top":305.5}},{"unicode":233,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.2278846153846153,"right":0.72307692307692306,"top":1.0028846153846156},"atlasBounds":{"left":96.5,"bottom":289.5,"right":107.5,"top":305.5}},{"unicode":234,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.2278846153846153,"right":0.72307692307692306,"top":1.0028846153846156},"atlasBounds":{"left":121.5,"bottom":289.5,"right":132.5,"top":305.5}},{"unicode":235,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.23188461538461524,"right":0.72307692307692306,"top":0.99888461538461559},"atlasBounds":{"left":133.5,"bottom":289.5,"right":144.5,"top":305.5}},{"unicode":236,"advance":0.59999999999999998,"planeBounds":{"left":-0.14153846153846145,"bottom":-0.22288461538461524,"right":0.78153846153846163,"top":1.0078846153846157},"atlasBounds":{"left":145.5,"bottom":289.5,"right":157.5,"top":305.5}},{"unicode":237,"advance":0.59999999999999998,"planeBounds":{"left":-0.14153846153846145,"bottom":-0.22288461538461524,"right":0.78153846153846163,"top":1.0078846153846157},"atlasBounds":{"left":158.5,"bottom":289.5,"right":170.5,"top":305.5}},{"unicode":238,"advance":0.59999999999999998,"planeBounds":{"left":-0.14153846153846145,"bottom":-0.22288461538461524,"right":0.78153846153846163,"top":1.0078846153846157},"atlasBounds":{"left":171.5,"bottom":289.5,"right":183.5,"top":305.5}},{"unicode":239,"advance":0.59999999999999998,"planeBounds":{"left":-0.14153846153846145,"bottom":-0.2268846153846153,"right":0.78153846153846163,"top":1.0038846153846155},"atlasBounds":{"left":184.5,"bottom":289.5,"right":196.5,"top":305.5}},{"unicode":240,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.21442307692307686,"right":0.72307692307692306,"top":0.93942307692307692},"atlasBounds":{"left":326.5,"bottom":190.5,"right":337.5,"top":205.5}},{"unicode":241,"advance":0.59999999999999998,"planeBounds":{"left":-0.12207692307692299,"bottom":-0.22538461538461529,"right":0.72407692307692306,"top":1.0053846153846155},"atlasBounds":{"left":197.5,"bottom":289.5,"right":208.5,"top":305.5}},{"unicode":242,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.2268846153846153,"right":0.72307692307692306,"top":1.0038846153846155},"atlasBounds":{"left":209.5,"bottom":289.5,"right":220.5,"top":305.5}},{"unicode":243,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.2268846153846153,"right":0.72307692307692306,"top":1.0038846153846155},"atlasBounds":{"left":221.5,"bottom":289.5,"right":232.5,"top":305.5}},{"unicode":244,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.2268846153846153,"right":0.72307692307692306,"top":1.0038846153846155},"atlasBounds":{"left":233.5,"bottom":289.5,"right":244.5,"top":305.5}},{"unicode":245,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.22938461538461524,"right":0.72307692307692306,"top":1.0013846153846155},"atlasBounds":{"left":245.5,"bottom":289.5,"right":256.5,"top":305.5}},{"unicode":246,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.23088461538461524,"right":0.72307692307692306,"top":0.99988461538461559},"atlasBounds":{"left":257.5,"bottom":289.5,"right":268.5,"top":305.5}},{"unicode":247,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.1674999999999999,"right":0.72307692307692306,"top":0.83250000000000002},"atlasBounds":{"left":263.5,"bottom":100.5,"right":274.5,"top":113.5}},{"unicode":248,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.25846153846153841,"right":0.76153846153846172,"top":0.81846153846153835},"atlasBounds":{"left":209.5,"bottom":143.5,"right":221.5,"top":157.5}},{"unicode":249,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2278846153846153,"right":0.72307692307692306,"top":1.0028846153846156},"atlasBounds":{"left":269.5,"bottom":289.5,"right":280.5,"top":305.5}},{"unicode":250,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2278846153846153,"right":0.72307692307692306,"top":1.0028846153846156},"atlasBounds":{"left":281.5,"bottom":289.5,"right":292.5,"top":305.5}},{"unicode":251,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2278846153846153,"right":0.72307692307692306,"top":1.0028846153846156},"atlasBounds":{"left":293.5,"bottom":289.5,"right":304.5,"top":305.5}},{"unicode":252,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.23188461538461524,"right":0.72307692307692306,"top":0.99888461538461559},"atlasBounds":{"left":305.5,"bottom":289.5,"right":316.5,"top":305.5}},{"unicode":253,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.3898076923076923,"right":0.76153846153846172,"top":0.99480769230769239},"atlasBounds":{"left":107.5,"bottom":399.5,"right":119.5,"top":417.5}},{"unicode":254,"advance":0.59999999999999998,"planeBounds":{"left":-0.12457692307692302,"bottom":-0.37384615384615377,"right":0.72157692307692312,"top":0.9338461538461541},"atlasBounds":{"left":54.5,"bottom":324.5,"right":65.5,"top":341.5}},{"unicode":255,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.39380769230769219,"right":0.76153846153846172,"top":0.99080769230769239},"atlasBounds":{"left":120.5,"bottom":399.5,"right":132.5,"top":417.5}},{"unicode":256,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.19634615384615375,"right":0.76153846153846172,"top":1.111346153846154},"atlasBounds":{"left":66.5,"bottom":324.5,"right":78.5,"top":341.5}},{"unicode":257,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.20942307692307682,"right":0.71057692307692311,"top":0.94442307692307692},"atlasBounds":{"left":386.5,"bottom":190.5,"right":397.5,"top":205.5}},{"unicode":258,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.21730769230769215,"right":0.76153846153846172,"top":1.1673076923076924},"atlasBounds":{"left":133.5,"bottom":399.5,"right":145.5,"top":417.5}},{"unicode":259,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.2278846153846153,"right":0.71057692307692311,"top":1.0028846153846156},"atlasBounds":{"left":344.5,"bottom":289.5,"right":355.5,"top":305.5}},{"unicode":260,"advance":0.59999999999999998,"planeBounds":{"left":-0.14403846153846145,"bottom":-0.42880769230769217,"right":0.77903846153846168,"top":0.95580769230769247},"atlasBounds":{"left":146.5,"bottom":399.5,"right":158.5,"top":417.5}},{"unicode":261,"advance":0.59999999999999998,"planeBounds":{"left":-0.14853846153846145,"bottom":-0.39842307692307688,"right":0.77453846153846173,"top":0.75542307692307697},"atlasBounds":{"left":410.5,"bottom":190.5,"right":422.5,"top":205.5}},{"unicode":262,"advance":0.59999999999999998,"planeBounds":{"left":-0.12007692307692296,"bottom":-0.22230769230769215,"right":0.72607692307692306,"top":1.1623076923076925},"atlasBounds":{"left":159.5,"bottom":399.5,"right":170.5,"top":417.5}},{"unicode":263,"advance":0.59999999999999998,"planeBounds":{"left":-0.12157692307692299,"bottom":-0.2278846153846153,"right":0.72457692307692312,"top":1.0028846153846156},"atlasBounds":{"left":368.5,"bottom":289.5,"right":379.5,"top":305.5}},{"unicode":264,"advance":0.59999999999999998,"planeBounds":{"left":-0.12007692307692296,"bottom":-0.22230769230769215,"right":0.72607692307692306,"top":1.1623076923076925},"atlasBounds":{"left":171.5,"bottom":399.5,"right":182.5,"top":417.5}},{"unicode":265,"advance":0.59999999999999998,"planeBounds":{"left":-0.12157692307692299,"bottom":-0.2278846153846153,"right":0.72457692307692312,"top":1.0028846153846156},"atlasBounds":{"left":395.5,"bottom":289.5,"right":406.5,"top":305.5}},{"unicode":266,"advance":0.59999999999999998,"planeBounds":{"left":-0.12007692307692296,"bottom":-0.22530769230769215,"right":0.72607692307692306,"top":1.1593076923076924},"atlasBounds":{"left":183.5,"bottom":399.5,"right":194.5,"top":417.5}},{"unicode":267,"advance":0.59999999999999998,"planeBounds":{"left":-0.12157692307692299,"bottom":-0.23188461538461524,"right":0.72457692307692312,"top":0.99888461538461559},"atlasBounds":{"left":407.5,"bottom":289.5,"right":418.5,"top":305.5}},{"unicode":268,"advance":0.59999999999999998,"planeBounds":{"left":-0.12007692307692296,"bottom":-0.22230769230769215,"right":0.72607692307692306,"top":1.1623076923076925},"atlasBounds":{"left":195.5,"bottom":399.5,"right":206.5,"top":417.5}},{"unicode":269,"advance":0.59999999999999998,"planeBounds":{"left":-0.12157692307692299,"bottom":-0.2278846153846153,"right":0.72457692307692312,"top":1.0028846153846156},"atlasBounds":{"left":419.5,"bottom":289.5,"right":430.5,"top":305.5}},{"unicode":270,"advance":0.59999999999999998,"planeBounds":{"left":-0.12107692307692298,"bottom":-0.21730769230769215,"right":0.72507692307692306,"top":1.1673076923076924},"atlasBounds":{"left":207.5,"bottom":399.5,"right":218.5,"top":417.5}},{"unicode":271,"advance":0.59999999999999998,"planeBounds":{"left":-0.13003846153846144,"bottom":-0.21692307692307686,"right":0.79303846153846169,"top":0.93692307692307697},"atlasBounds":{"left":460.5,"bottom":190.5,"right":472.5,"top":205.5}},{"unicode":272,"advance":0.59999999999999998,"planeBounds":{"left":-0.20053846153846144,"bottom":-0.2119230769230768,"right":0.72253846153846168,"top":0.94192307692307697},"atlasBounds":{"left":473.5,"bottom":190.5,"right":485.5,"top":205.5}},{"unicode":273,"advance":0.59999999999999998,"planeBounds":{"left":-0.11653846153846147,"bottom":-0.21692307692307686,"right":0.80653846153846165,"top":0.93692307692307697},"atlasBounds":{"left":12.5,"bottom":174.5,"right":24.5,"top":189.5}},{"unicode":274,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692298,"bottom":-0.19634615384615375,"right":0.73307692307692307,"top":1.111346153846154},"atlasBounds":{"left":169.5,"bottom":324.5,"right":180.5,"top":341.5}},{"unicode":275,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.20942307692307682,"right":0.72307692307692306,"top":0.94442307692307692},"atlasBounds":{"left":62.5,"bottom":174.5,"right":73.5,"top":189.5}},{"unicode":276,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692298,"bottom":-0.21730769230769215,"right":0.73307692307692307,"top":1.1673076923076924},"atlasBounds":{"left":219.5,"bottom":399.5,"right":230.5,"top":417.5}},{"unicode":277,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.2278846153846153,"right":0.72307692307692306,"top":1.0028846153846156},"atlasBounds":{"left":458.5,"bottom":289.5,"right":469.5,"top":305.5}},{"unicode":278,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692298,"bottom":-0.22030769230769226,"right":0.73307692307692307,"top":1.1643076923076925},"atlasBounds":{"left":231.5,"bottom":399.5,"right":242.5,"top":417.5}},{"unicode":279,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.23188461538461524,"right":0.72307692307692306,"top":0.99888461538461559},"atlasBounds":{"left":470.5,"bottom":289.5,"right":481.5,"top":305.5}},{"unicode":280,"advance":0.59999999999999998,"planeBounds":{"left":-0.11057692307692302,"bottom":-0.42880769230769217,"right":0.73557692307692313,"top":0.95580769230769247},"atlasBounds":{"left":243.5,"bottom":399.5,"right":254.5,"top":417.5}},{"unicode":281,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.39842307692307688,"right":0.72307692307692306,"top":0.75542307692307697},"atlasBounds":{"left":74.5,"bottom":174.5,"right":85.5,"top":189.5}},{"unicode":282,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692298,"bottom":-0.21730769230769215,"right":0.73307692307692307,"top":1.1673076923076924},"atlasBounds":{"left":255.5,"bottom":399.5,"right":266.5,"top":417.5}},{"unicode":283,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.2278846153846153,"right":0.72307692307692306,"top":1.0028846153846156},"atlasBounds":{"left":482.5,"bottom":289.5,"right":493.5,"top":305.5}},{"unicode":284,"advance":0.59999999999999998,"planeBounds":{"left":-0.12007692307692296,"bottom":-0.22230769230769215,"right":0.72607692307692306,"top":1.1623076923076925},"atlasBounds":{"left":267.5,"bottom":399.5,"right":278.5,"top":417.5}},{"unicode":285,"advance":0.59999999999999998,"planeBounds":{"left":-0.12557692307692297,"bottom":-0.3898076923076923,"right":0.72057692307692311,"top":0.99480769230769239},"atlasBounds":{"left":279.5,"bottom":399.5,"right":290.5,"top":417.5}},{"unicode":286,"advance":0.59999999999999998,"planeBounds":{"left":-0.12007692307692296,"bottom":-0.22230769230769215,"right":0.72607692307692306,"top":1.1623076923076925},"atlasBounds":{"left":291.5,"bottom":399.5,"right":302.5,"top":417.5}},{"unicode":287,"advance":0.59999999999999998,"planeBounds":{"left":-0.12557692307692297,"bottom":-0.3898076923076923,"right":0.72057692307692311,"top":0.99480769230769239},"atlasBounds":{"left":325.5,"bottom":399.5,"right":336.5,"top":417.5}},{"unicode":288,"advance":0.59999999999999998,"planeBounds":{"left":-0.12007692307692296,"bottom":-0.22530769230769215,"right":0.72607692307692306,"top":1.1593076923076924},"atlasBounds":{"left":337.5,"bottom":399.5,"right":348.5,"top":417.5}},{"unicode":289,"advance":0.59999999999999998,"planeBounds":{"left":-0.12557692307692297,"bottom":-0.39380769230769219,"right":0.72057692307692311,"top":0.99080769230769239},"atlasBounds":{"left":349.5,"bottom":399.5,"right":360.5,"top":417.5}},{"unicode":290,"advance":0.59999999999999998,"planeBounds":{"left":-0.12007692307692296,"bottom":-0.44230769230769229,"right":0.72607692307692306,"top":0.9423076923076924},"atlasBounds":{"left":361.5,"bottom":399.5,"right":372.5,"top":417.5}},{"unicode":291,"advance":0.59999999999999998,"planeBounds":{"left":-0.12557692307692297,"bottom":-0.38076923076923064,"right":0.72057692307692311,"top":1.0807692307692309},"atlasBounds":{"left":188.5,"bottom":440.5,"right":199.5,"top":459.5}},{"unicode":292,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.21730769230769215,"right":0.72307692307692306,"top":1.1673076923076924},"atlasBounds":{"left":373.5,"bottom":399.5,"right":384.5,"top":417.5}},{"unicode":293,"advance":0.59999999999999998,"planeBounds":{"left":-0.2604999999999999,"bottom":-0.21980769230769215,"right":0.73950000000000005,"top":1.1648076923076924},"atlasBounds":{"left":398.5,"bottom":399.5,"right":411.5,"top":417.5}},{"unicode":294,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.2119230769230768,"right":0.80000000000000004,"top":0.94192307692307697},"atlasBounds":{"left":99.5,"bottom":174.5,"right":112.5,"top":189.5}},{"unicode":295,"advance":0.59999999999999998,"planeBounds":{"left":-0.20653846153846148,"bottom":-0.2119230769230768,"right":0.71653846153846168,"top":0.94192307692307697},"atlasBounds":{"left":113.5,"bottom":174.5,"right":125.5,"top":189.5}},{"unicode":296,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.21980769230769215,"right":0.72307692307692306,"top":1.1648076923076924},"atlasBounds":{"left":412.5,"bottom":399.5,"right":423.5,"top":417.5}},{"unicode":297,"advance":0.59999999999999998,"planeBounds":{"left":-0.14153846153846145,"bottom":-0.22538461538461529,"right":0.78153846153846163,"top":1.0053846153846155},"atlasBounds":{"left":494.5,"bottom":289.5,"right":506.5,"top":305.5}},{"unicode":298,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.19634615384615375,"right":0.72307692307692306,"top":1.111346153846154},"atlasBounds":{"left":181.5,"bottom":324.5,"right":192.5,"top":341.5}},{"unicode":299,"advance":0.59999999999999998,"planeBounds":{"left":-0.14153846153846145,"bottom":-0.20442307692307685,"right":0.78153846153846163,"top":0.94942307692307693},"atlasBounds":{"left":126.5,"bottom":174.5,"right":138.5,"top":189.5}},{"unicode":300,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.21730769230769215,"right":0.72307692307692306,"top":1.1673076923076924},"atlasBounds":{"left":424.5,"bottom":399.5,"right":435.5,"top":417.5}},{"unicode":301,"advance":0.59999999999999998,"planeBounds":{"left":-0.14153846153846145,"bottom":-0.22288461538461524,"right":0.78153846153846163,"top":1.0078846153846157},"atlasBounds":{"left":0.5,"bottom":272.5,"right":12.5,"top":288.5}},{"unicode":302,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.42880769230769217,"right":0.72307692307692306,"top":0.95580769230769247},"atlasBounds":{"left":436.5,"bottom":399.5,"right":447.5,"top":417.5}},{"unicode":303,"advance":0.59999999999999998,"planeBounds":{"left":-0.14153846153846145,"bottom":-0.40530769230769226,"right":0.78153846153846163,"top":0.97930769230769243},"atlasBounds":{"left":448.5,"bottom":399.5,"right":460.5,"top":417.5}},{"unicode":304,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.22030769230769226,"right":0.72307692307692306,"top":1.1643076923076925},"atlasBounds":{"left":461.5,"bottom":399.5,"right":472.5,"top":417.5}},{"unicode":305,"advance":0.59999999999999998,"planeBounds":{"left":-0.14153846153846145,"bottom":-0.22499999999999995,"right":0.78153846153846163,"top":0.77500000000000002},"atlasBounds":{"left":14.5,"bottom":100.5,"right":26.5,"top":113.5}},{"unicode":308,"advance":0.59999999999999998,"planeBounds":{"left":-0.16853846153846147,"bottom":-0.22230769230769215,"right":0.75453846153846171,"top":1.1623076923076925},"atlasBounds":{"left":473.5,"bottom":399.5,"right":485.5,"top":417.5}},{"unicode":309,"advance":0.59999999999999998,"planeBounds":{"left":-0.14353846153846148,"bottom":-0.3898076923076923,"right":0.77953846153846162,"top":0.99480769230769239},"atlasBounds":{"left":486.5,"bottom":399.5,"right":498.5,"top":417.5}},{"unicode":310,"advance":0.59999999999999998,"planeBounds":{"left":-0.13553846153846144,"bottom":-0.44730769230769224,"right":0.78753846153846163,"top":0.9373076923076924},"atlasBounds":{"left":499.5,"bottom":399.5,"right":511.5,"top":417.5}},{"unicode":311,"advance":0.59999999999999998,"planeBounds":{"left":-0.1355384615384615,"bottom":-0.44730769230769224,"right":0.78753846153846163,"top":0.9373076923076924},"atlasBounds":{"left":0.5,"bottom":380.5,"right":12.5,"top":398.5}},{"unicode":312,"advance":0.59999999999999998,"planeBounds":{"left":-0.1355384615384615,"bottom":-0.22499999999999995,"right":0.78753846153846163,"top":0.77500000000000002},"atlasBounds":{"left":457.5,"bottom":114.5,"right":469.5,"top":127.5}},{"unicode":313,"advance":0.59999999999999998,"planeBounds":{"left":-0.089576923076922985,"bottom":-0.21730769230769215,"right":0.75657692307692315,"top":1.1673076923076924},"atlasBounds":{"left":13.5,"bottom":380.5,"right":24.5,"top":398.5}},{"unicode":314,"advance":0.59999999999999998,"planeBounds":{"left":-0.17153846153846145,"bottom":-0.21730769230769215,"right":0.75153846153846171,"top":1.1673076923076924},"atlasBounds":{"left":25.5,"bottom":380.5,"right":37.5,"top":398.5}},{"unicode":315,"advance":0.59999999999999998,"planeBounds":{"left":-0.083076923076922979,"bottom":-0.44730769230769224,"right":0.7630769230769231,"top":0.9373076923076924},"atlasBounds":{"left":38.5,"bottom":380.5,"right":49.5,"top":398.5}},{"unicode":316,"advance":0.59999999999999998,"planeBounds":{"left":-0.17153846153846145,"bottom":-0.44730769230769224,"right":0.75153846153846171,"top":0.9373076923076924},"atlasBounds":{"left":50.5,"bottom":380.5,"right":62.5,"top":398.5}},{"unicode":317,"advance":0.59999999999999998,"planeBounds":{"left":-0.087499999999999911,"bottom":-0.19884615384615378,"right":0.91249999999999998,"top":1.108846153846154},"atlasBounds":{"left":274.5,"bottom":324.5,"right":287.5,"top":341.5}},{"unicode":318,"advance":0.59999999999999998,"planeBounds":{"left":-0.17596153846153836,"bottom":-0.19884615384615378,"right":0.90096153846153848,"top":1.108846153846154},"atlasBounds":{"left":288.5,"bottom":324.5,"right":302.5,"top":341.5}},{"unicode":319,"advance":0.59999999999999998,"planeBounds":{"left":-0.083076923076922979,"bottom":-0.2119230769230768,"right":0.7630769230769231,"top":0.94192307692307697},"atlasBounds":{"left":152.5,"bottom":174.5,"right":163.5,"top":189.5}},{"unicode":320,"advance":0.59999999999999998,"planeBounds":{"left":-0.2184615384615384,"bottom":-0.2119230769230768,"right":0.85846153846153839,"top":0.94192307692307697},"atlasBounds":{"left":463.5,"bottom":174.5,"right":477.5,"top":189.5}},{"unicode":321,"advance":0.59999999999999998,"planeBounds":{"left":-0.17653846153846148,"bottom":-0.2119230769230768,"right":0.74653846153846171,"top":0.94192307692307697},"atlasBounds":{"left":58.5,"bottom":158.5,"right":70.5,"top":173.5}},{"unicode":322,"advance":0.59999999999999998,"planeBounds":{"left":-0.17153846153846145,"bottom":-0.2119230769230768,"right":0.75153846153846171,"top":0.94192307692307697},"atlasBounds":{"left":85.5,"bottom":158.5,"right":97.5,"top":173.5}},{"unicode":323,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.21730769230769215,"right":0.72307692307692306,"top":1.1673076923076924},"atlasBounds":{"left":63.5,"bottom":380.5,"right":74.5,"top":398.5}},{"unicode":324,"advance":0.59999999999999998,"planeBounds":{"left":-0.12207692307692299,"bottom":-0.22288461538461524,"right":0.72407692307692306,"top":1.0078846153846157},"atlasBounds":{"left":82.5,"bottom":272.5,"right":93.5,"top":288.5}},{"unicode":325,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.44730769230769224,"right":0.72307692307692306,"top":0.9373076923076924},"atlasBounds":{"left":75.5,"bottom":380.5,"right":86.5,"top":398.5}},{"unicode":326,"advance":0.59999999999999998,"planeBounds":{"left":-0.12207692307692299,"bottom":-0.45538461538461528,"right":0.72407692307692306,"top":0.77538461538461556},"atlasBounds":{"left":106.5,"bottom":272.5,"right":117.5,"top":288.5}},{"unicode":327,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.21730769230769215,"right":0.72307692307692306,"top":1.1673076923076924},"atlasBounds":{"left":87.5,"bottom":380.5,"right":98.5,"top":398.5}},{"unicode":328,"advance":0.59999999999999998,"planeBounds":{"left":-0.12207692307692299,"bottom":-0.22288461538461524,"right":0.72407692307692306,"top":1.0078846153846157},"atlasBounds":{"left":118.5,"bottom":272.5,"right":129.5,"top":288.5}},{"unicode":329,"advance":0.59999999999999998,"planeBounds":{"left":-0.21153846153846143,"bottom":-0.22038461538461532,"right":0.71153846153846168,"top":1.0103846153846154},"atlasBounds":{"left":130.5,"bottom":272.5,"right":142.5,"top":288.5}},{"unicode":330,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.37884615384615383,"right":0.72307692307692306,"top":0.92884615384615399},"atlasBounds":{"left":339.5,"bottom":324.5,"right":350.5,"top":341.5}},{"unicode":331,"advance":0.59999999999999998,"planeBounds":{"left":-0.12207692307692299,"bottom":-0.38692307692307687,"right":0.72407692307692306,"top":0.76692307692307693},"atlasBounds":{"left":124.5,"bottom":158.5,"right":135.5,"top":173.5}},{"unicode":332,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.23980769230769228,"right":0.72307692307692306,"top":1.1448076923076924},"atlasBounds":{"left":99.5,"bottom":380.5,"right":110.5,"top":398.5}},{"unicode":333,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.20842307692307682,"right":0.72307692307692306,"top":0.94542307692307692},"atlasBounds":{"left":136.5,"bottom":158.5,"right":147.5,"top":173.5}},{"unicode":334,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.22230769230769215,"right":0.72307692307692306,"top":1.1623076923076925},"atlasBounds":{"left":111.5,"bottom":380.5,"right":122.5,"top":398.5}},{"unicode":335,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.2268846153846153,"right":0.72307692307692306,"top":1.0038846153846155},"atlasBounds":{"left":168.5,"bottom":272.5,"right":179.5,"top":288.5}},{"unicode":336,"advance":0.59999999999999998,"planeBounds":{"left":-0.11507692307692301,"bottom":-0.22230769230769215,"right":0.73107692307692307,"top":1.1623076923076925},"atlasBounds":{"left":123.5,"bottom":380.5,"right":134.5,"top":398.5}},{"unicode":337,"advance":0.59999999999999998,"planeBounds":{"left":-0.11657692307692301,"bottom":-0.2268846153846153,"right":0.72957692307692312,"top":1.0038846153846155},"atlasBounds":{"left":180.5,"bottom":272.5,"right":191.5,"top":288.5}},{"unicode":338,"advance":0.59999999999999998,"planeBounds":{"left":-0.15403846153846143,"bottom":-0.2119230769230768,"right":0.76903846153846167,"top":0.94192307692307697},"atlasBounds":{"left":161.5,"bottom":158.5,"right":173.5,"top":173.5}},{"unicode":339,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.22499999999999989,"right":0.76153846153846172,"top":0.77500000000000002},"atlasBounds":{"left":116.5,"bottom":114.5,"right":128.5,"top":127.5}},{"unicode":340,"advance":0.59999999999999998,"planeBounds":{"left":-0.10457692307692298,"bottom":-0.21730769230769215,"right":0.74157692307692313,"top":1.1673076923076924},"atlasBounds":{"left":149.5,"bottom":380.5,"right":160.5,"top":398.5}},{"unicode":341,"advance":0.59999999999999998,"planeBounds":{"left":-0.10157692307692301,"bottom":-0.22288461538461524,"right":0.74457692307692314,"top":1.0078846153846157},"atlasBounds":{"left":192.5,"bottom":272.5,"right":203.5,"top":288.5}},{"unicode":342,"advance":0.59999999999999998,"planeBounds":{"left":-0.10457692307692298,"bottom":-0.44730769230769224,"right":0.74157692307692313,"top":0.9373076923076924},"atlasBounds":{"left":161.5,"bottom":380.5,"right":172.5,"top":398.5}},{"unicode":343,"advance":0.59999999999999998,"planeBounds":{"left":-0.17703846153846142,"bottom":-0.45538461538461528,"right":0.74603846153846165,"top":0.77538461538461556},"atlasBounds":{"left":204.5,"bottom":272.5,"right":216.5,"top":288.5}},{"unicode":344,"advance":0.59999999999999998,"planeBounds":{"left":-0.10457692307692298,"bottom":-0.21730769230769215,"right":0.74157692307692313,"top":1.1673076923076924},"atlasBounds":{"left":173.5,"bottom":380.5,"right":184.5,"top":398.5}},{"unicode":345,"advance":0.59999999999999998,"planeBounds":{"left":-0.10157692307692301,"bottom":-0.22288461538461524,"right":0.74457692307692314,"top":1.0078846153846157},"atlasBounds":{"left":217.5,"bottom":272.5,"right":228.5,"top":288.5}},{"unicode":346,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22230769230769215,"right":0.72307692307692306,"top":1.1623076923076925},"atlasBounds":{"left":185.5,"bottom":380.5,"right":196.5,"top":398.5}},{"unicode":347,"advance":0.59999999999999998,"planeBounds":{"left":-0.11807692307692295,"bottom":-0.2268846153846153,"right":0.72807692307692307,"top":1.0038846153846155},"atlasBounds":{"left":229.5,"bottom":272.5,"right":240.5,"top":288.5}},{"unicode":348,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22230769230769215,"right":0.72307692307692306,"top":1.1623076923076925},"atlasBounds":{"left":197.5,"bottom":380.5,"right":208.5,"top":398.5}},{"unicode":349,"advance":0.59999999999999998,"planeBounds":{"left":-0.11807692307692295,"bottom":-0.2268846153846153,"right":0.72807692307692307,"top":1.0038846153846155},"atlasBounds":{"left":269.5,"bottom":272.5,"right":280.5,"top":288.5}},{"unicode":350,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.42380769230769216,"right":0.72307692307692306,"top":0.96080769230769247},"atlasBounds":{"left":209.5,"bottom":380.5,"right":220.5,"top":398.5}},{"unicode":351,"advance":0.59999999999999998,"planeBounds":{"left":-0.11807692307692295,"bottom":-0.39942307692307688,"right":0.72807692307692307,"top":0.75442307692307697},"atlasBounds":{"left":174.5,"bottom":158.5,"right":185.5,"top":173.5}},{"unicode":352,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22230769230769215,"right":0.72307692307692306,"top":1.1623076923076925},"atlasBounds":{"left":221.5,"bottom":380.5,"right":232.5,"top":398.5}},{"unicode":353,"advance":0.59999999999999998,"planeBounds":{"left":-0.11807692307692295,"bottom":-0.2268846153846153,"right":0.72807692307692307,"top":1.0038846153846155},"atlasBounds":{"left":294.5,"bottom":272.5,"right":305.5,"top":288.5}},{"unicode":354,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.42880769230769217,"right":0.76153846153846172,"top":0.95580769230769247},"atlasBounds":{"left":233.5,"bottom":380.5,"right":245.5,"top":398.5}},{"unicode":355,"advance":0.59999999999999998,"planeBounds":{"left":-0.17403846153846145,"bottom":-0.4028461538461538,"right":0.74903846153846165,"top":0.90484615384615397},"atlasBounds":{"left":351.5,"bottom":324.5,"right":363.5,"top":341.5}},{"unicode":356,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.21730769230769215,"right":0.76153846153846172,"top":1.1673076923076924},"atlasBounds":{"left":246.5,"bottom":380.5,"right":258.5,"top":398.5}},{"unicode":357,"advance":0.59999999999999998,"planeBounds":{"left":-0.15803846153846149,"bottom":-0.2273846153846153,"right":0.76503846153846167,"top":1.0033846153846155},"atlasBounds":{"left":319.5,"bottom":272.5,"right":331.5,"top":288.5}},{"unicode":358,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":210.5,"bottom":158.5,"right":222.5,"top":173.5}},{"unicode":359,"advance":0.59999999999999998,"planeBounds":{"left":-0.17403846153846145,"bottom":-0.22442307692307686,"right":0.74903846153846165,"top":0.92942307692307691},"atlasBounds":{"left":223.5,"bottom":158.5,"right":235.5,"top":173.5}},{"unicode":360,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22480769230769221,"right":0.72307692307692306,"top":1.1598076923076923},"atlasBounds":{"left":259.5,"bottom":380.5,"right":270.5,"top":398.5}},{"unicode":361,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2303846153846153,"right":0.72307692307692306,"top":1.0003846153846154},"atlasBounds":{"left":345.5,"bottom":272.5,"right":356.5,"top":288.5}},{"unicode":362,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.23980769230769228,"right":0.72307692307692306,"top":1.1448076923076924},"atlasBounds":{"left":271.5,"bottom":380.5,"right":282.5,"top":398.5}},{"unicode":363,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.20942307692307682,"right":0.72307692307692306,"top":0.94442307692307692},"atlasBounds":{"left":236.5,"bottom":158.5,"right":247.5,"top":173.5}},{"unicode":364,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22230769230769215,"right":0.72307692307692306,"top":1.1623076923076925},"atlasBounds":{"left":283.5,"bottom":380.5,"right":294.5,"top":398.5}},{"unicode":365,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2278846153846153,"right":0.72307692307692306,"top":1.0028846153846156},"atlasBounds":{"left":369.5,"bottom":272.5,"right":380.5,"top":288.5}},{"unicode":366,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.23876923076923068,"right":0.72307692307692306,"top":1.2227692307692311},"atlasBounds":{"left":200.5,"bottom":440.5,"right":211.5,"top":459.5}},{"unicode":367,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2023846153846153,"right":0.72307692307692306,"top":1.0283846153846155},"atlasBounds":{"left":381.5,"bottom":272.5,"right":392.5,"top":288.5}},{"unicode":368,"advance":0.59999999999999998,"planeBounds":{"left":-0.11407692307692298,"bottom":-0.22230769230769215,"right":0.73207692307692307,"top":1.1623076923076925},"atlasBounds":{"left":295.5,"bottom":380.5,"right":306.5,"top":398.5}},{"unicode":369,"advance":0.59999999999999998,"planeBounds":{"left":-0.11407692307692298,"bottom":-0.2278846153846153,"right":0.73207692307692307,"top":1.0028846153846156},"atlasBounds":{"left":393.5,"bottom":272.5,"right":404.5,"top":288.5}},{"unicode":370,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.42880769230769217,"right":0.72307692307692306,"top":0.95580769230769247},"atlasBounds":{"left":307.5,"bottom":380.5,"right":318.5,"top":398.5}},{"unicode":371,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.40342307692307677,"right":0.72307692307692306,"top":0.75042307692307697},"atlasBounds":{"left":260.5,"bottom":158.5,"right":271.5,"top":173.5}},{"unicode":372,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999996,"bottom":-0.21730769230769215,"right":0.80000000000000004,"top":1.1673076923076924},"atlasBounds":{"left":319.5,"bottom":380.5,"right":332.5,"top":398.5}},{"unicode":373,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.22288461538461524,"right":0.76153846153846172,"top":1.0078846153846157},"atlasBounds":{"left":405.5,"bottom":272.5,"right":417.5,"top":288.5}},{"unicode":374,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.21730769230769215,"right":0.76153846153846172,"top":1.1673076923076924},"atlasBounds":{"left":333.5,"bottom":380.5,"right":345.5,"top":398.5}},{"unicode":375,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.3898076923076923,"right":0.76153846153846172,"top":0.99480769230769239},"atlasBounds":{"left":346.5,"bottom":380.5,"right":358.5,"top":398.5}},{"unicode":376,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.21980769230769215,"right":0.76153846153846172,"top":1.1648076923076924},"atlasBounds":{"left":359.5,"bottom":380.5,"right":371.5,"top":398.5}},{"unicode":377,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.21730769230769215,"right":0.72307692307692306,"top":1.1673076923076924},"atlasBounds":{"left":372.5,"bottom":380.5,"right":383.5,"top":398.5}},{"unicode":378,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22288461538461524,"right":0.72307692307692306,"top":1.0078846153846157},"atlasBounds":{"left":418.5,"bottom":272.5,"right":429.5,"top":288.5}},{"unicode":379,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.22030769230769226,"right":0.72307692307692306,"top":1.1643076923076925},"atlasBounds":{"left":384.5,"bottom":380.5,"right":395.5,"top":398.5}},{"unicode":380,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2268846153846153,"right":0.72307692307692306,"top":1.0038846153846155},"atlasBounds":{"left":442.5,"bottom":272.5,"right":453.5,"top":288.5}},{"unicode":381,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.21730769230769215,"right":0.72307692307692306,"top":1.1673076923076924},"atlasBounds":{"left":396.5,"bottom":380.5,"right":407.5,"top":398.5}},{"unicode":382,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22288461538461524,"right":0.72307692307692306,"top":1.0078846153846157},"atlasBounds":{"left":466.5,"bottom":272.5,"right":477.5,"top":288.5}},{"unicode":383,"advance":0.59999999999999998,"planeBounds":{"left":-0.047115384615384524,"bottom":-0.2119230769230768,"right":0.7221153846153846,"top":0.94192307692307697},"atlasBounds":{"left":272.5,"bottom":158.5,"right":282.5,"top":173.5}},{"unicode":399,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":283.5,"bottom":158.5,"right":294.5,"top":173.5}},{"unicode":400,"advance":0.59999999999999998,"planeBounds":{"left":-0.15353846153846146,"bottom":-0.2119230769230768,"right":0.76953846153846173,"top":0.94192307692307697},"atlasBounds":{"left":295.5,"bottom":158.5,"right":307.5,"top":173.5}},{"unicode":402,"advance":0.59999999999999998,"planeBounds":{"left":-0.19403846153846144,"bottom":-0.37884615384615383,"right":0.72903846153846164,"top":0.92884615384615399},"atlasBounds":{"left":364.5,"bottom":324.5,"right":376.5,"top":341.5}},{"unicode":411,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.20692307692307685,"right":0.76153846153846172,"top":0.94692307692307698},"atlasBounds":{"left":308.5,"bottom":158.5,"right":320.5,"top":173.5}},{"unicode":416,"advance":0.59999999999999998,"planeBounds":{"left":-0.11407692307692301,"bottom":-0.21538461538461526,"right":0.73207692307692307,"top":1.0153846153846156},"atlasBounds":{"left":490.5,"bottom":272.5,"right":501.5,"top":288.5}},{"unicode":417,"advance":0.59999999999999998,"planeBounds":{"left":-0.11057692307692299,"bottom":-0.22246153846153835,"right":0.73557692307692313,"top":0.85446153846153849},"atlasBounds":{"left":480.5,"bottom":143.5,"right":491.5,"top":157.5}},{"unicode":431,"advance":0.59999999999999998,"planeBounds":{"left":-0.11903846153846144,"bottom":-0.23884615384615371,"right":0.8040384615384617,"top":1.068846153846154},"atlasBounds":{"left":377.5,"bottom":324.5,"right":389.5,"top":341.5}},{"unicode":432,"advance":0.59999999999999998,"planeBounds":{"left":-0.11653846153846147,"bottom":-0.21346153846153837,"right":0.80653846153846165,"top":0.8634615384615385},"atlasBounds":{"left":459.5,"bottom":143.5,"right":471.5,"top":157.5}},{"unicode":461,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.21730769230769215,"right":0.76153846153846172,"top":1.1673076923076924},"atlasBounds":{"left":408.5,"bottom":380.5,"right":420.5,"top":398.5}},{"unicode":462,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.2278846153846153,"right":0.71057692307692311,"top":1.0028846153846156},"atlasBounds":{"left":12.5,"bottom":255.5,"right":23.5,"top":271.5}},{"unicode":464,"advance":0.59999999999999998,"planeBounds":{"left":-0.14153846153846145,"bottom":-0.22288461538461524,"right":0.78153846153846163,"top":1.0078846153846157},"atlasBounds":{"left":24.5,"bottom":255.5,"right":36.5,"top":271.5}},{"unicode":466,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.2268846153846153,"right":0.72307692307692306,"top":1.0038846153846155},"atlasBounds":{"left":37.5,"bottom":255.5,"right":48.5,"top":271.5}},{"unicode":468,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2278846153846153,"right":0.72307692307692306,"top":1.0028846153846156},"atlasBounds":{"left":49.5,"bottom":255.5,"right":60.5,"top":271.5}},{"unicode":470,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.21130769230769228,"right":0.72307692307692306,"top":1.1733076923076924},"atlasBounds":{"left":421.5,"bottom":380.5,"right":432.5,"top":398.5}},{"unicode":472,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22976923076923061,"right":0.72307692307692306,"top":1.2317692307692309},"atlasBounds":{"left":212.5,"bottom":440.5,"right":223.5,"top":459.5}},{"unicode":474,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22976923076923061,"right":0.72307692307692306,"top":1.2317692307692309},"atlasBounds":{"left":224.5,"bottom":440.5,"right":235.5,"top":459.5}},{"unicode":476,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22976923076923061,"right":0.72307692307692306,"top":1.2317692307692309},"atlasBounds":{"left":236.5,"bottom":440.5,"right":247.5,"top":459.5}},{"unicode":486,"advance":0.59999999999999998,"planeBounds":{"left":-0.12007692307692296,"bottom":-0.22230769230769215,"right":0.72607692307692306,"top":1.1623076923076925},"atlasBounds":{"left":433.5,"bottom":380.5,"right":444.5,"top":398.5}},{"unicode":487,"advance":0.59999999999999998,"planeBounds":{"left":-0.12557692307692297,"bottom":-0.3898076923076923,"right":0.72057692307692311,"top":0.99480769230769239},"atlasBounds":{"left":445.5,"bottom":380.5,"right":456.5,"top":398.5}},{"unicode":490,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.42380769230769216,"right":0.72307692307692306,"top":0.96080769230769247},"atlasBounds":{"left":457.5,"bottom":380.5,"right":468.5,"top":398.5}},{"unicode":491,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.39842307692307688,"right":0.72307692307692306,"top":0.75542307692307697},"atlasBounds":{"left":333.5,"bottom":158.5,"right":344.5,"top":173.5}},{"unicode":500,"advance":0.59999999999999998,"planeBounds":{"left":-0.12007692307692296,"bottom":-0.22230769230769215,"right":0.72607692307692306,"top":1.1623076923076925},"atlasBounds":{"left":469.5,"bottom":380.5,"right":480.5,"top":398.5}},{"unicode":501,"advance":0.59999999999999998,"planeBounds":{"left":-0.12557692307692297,"bottom":-0.3898076923076923,"right":0.72057692307692311,"top":0.99480769230769239},"atlasBounds":{"left":481.5,"bottom":380.5,"right":492.5,"top":398.5}},{"unicode":508,"advance":0.59999999999999998,"planeBounds":{"left":-0.1999999999999999,"bottom":-0.21730769230769215,"right":0.80000000000000004,"top":1.1673076923076924},"atlasBounds":{"left":493.5,"bottom":380.5,"right":506.5,"top":398.5}},{"unicode":509,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.2278846153846153,"right":0.76153846153846172,"top":1.0028846153846156},"atlasBounds":{"left":61.5,"bottom":255.5,"right":73.5,"top":271.5}},{"unicode":510,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.23730769230769216,"right":0.76153846153846172,"top":1.1473076923076924},"atlasBounds":{"left":0.5,"bottom":361.5,"right":12.5,"top":379.5}},{"unicode":511,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.23788461538461531,"right":0.76153846153846172,"top":0.99288461538461548},"atlasBounds":{"left":74.5,"bottom":255.5,"right":86.5,"top":271.5}},{"unicode":536,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.44230769230769229,"right":0.72307692307692306,"top":0.9423076923076924},"atlasBounds":{"left":13.5,"bottom":361.5,"right":24.5,"top":379.5}},{"unicode":537,"advance":0.59999999999999998,"planeBounds":{"left":-0.11807692307692295,"bottom":-0.45638461538461533,"right":0.72807692307692307,"top":0.77438461538461545},"atlasBounds":{"left":87.5,"bottom":255.5,"right":98.5,"top":271.5}},{"unicode":538,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.44730769230769224,"right":0.76153846153846172,"top":0.9373076923076924},"atlasBounds":{"left":25.5,"bottom":361.5,"right":37.5,"top":379.5}},{"unicode":539,"advance":0.59999999999999998,"planeBounds":{"left":-0.17403846153846145,"bottom":-0.45980769230769214,"right":0.74903846153846165,"top":0.92480769230769255},"atlasBounds":{"left":38.5,"bottom":361.5,"right":50.5,"top":379.5}},{"unicode":562,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.19634615384615375,"right":0.76153846153846172,"top":1.111346153846154},"atlasBounds":{"left":390.5,"bottom":324.5,"right":402.5,"top":341.5}},{"unicode":563,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.40980769230769226,"right":0.76153846153846172,"top":0.97480769230769237},"atlasBounds":{"left":51.5,"bottom":361.5,"right":63.5,"top":379.5}},{"unicode":567,"advance":0.59999999999999998,"planeBounds":{"left":-0.13211538461538452,"bottom":-0.39192307692307682,"right":0.63711538461538464,"top":0.76192307692307693},"atlasBounds":{"left":359.5,"bottom":158.5,"right":369.5,"top":173.5}},{"unicode":601,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22499999999999989,"right":0.72307692307692306,"top":0.77500000000000002},"atlasBounds":{"left":478.5,"bottom":129.5,"right":489.5,"top":142.5}},{"unicode":697,"advance":0.59999999999999998,"planeBounds":{"left":-0.037692307692307615,"bottom":0.18088461538461548,"right":0.57769230769230773,"top":0.95011538461538469},"atlasBounds":{"left":247.5,"bottom":76.5,"right":255.5,"top":86.5}},{"unicode":698,"advance":0.59999999999999998,"planeBounds":{"left":-0.16403846153846144,"bottom":0.17988461538461548,"right":0.75903846153846166,"top":0.94911538461538469},"atlasBounds":{"left":234.5,"bottom":76.5,"right":246.5,"top":86.5}},{"unicode":700,"advance":0.59999999999999998,"planeBounds":{"left":-0.037692307692307615,"bottom":0.23588461538461547,"right":0.57769230769230773,"top":1.0051153846153846},"atlasBounds":{"left":175.5,"bottom":76.5,"right":183.5,"top":86.5}},{"unicode":710,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384495,"bottom":0.44576923076923086,"right":0.68461538461538463,"top":0.98423076923076935},"atlasBounds":{"left":483.5,"bottom":57.5,"right":493.5,"top":64.5}},{"unicode":711,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384495,"bottom":0.44576923076923086,"right":0.68461538461538463,"top":0.98423076923076935},"atlasBounds":{"left":416.5,"bottom":57.5,"right":426.5,"top":64.5}},{"unicode":713,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384565,"bottom":0.47673076923076929,"right":0.68461538461538463,"top":0.93826923076923086},"atlasBounds":{"left":239.5,"bottom":48.5,"right":249.5,"top":54.5}},{"unicode":728,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384565,"bottom":0.44326923076923086,"right":0.68461538461538463,"top":0.9817307692307693},"atlasBounds":{"left":173.5,"bottom":47.5,"right":183.5,"top":54.5}},{"unicode":729,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230868,"bottom":0.44376923076923086,"right":0.56923076923076932,"top":0.98223076923076935},"atlasBounds":{"left":395.5,"bottom":57.5,"right":402.5,"top":64.5}},{"unicode":730,"advance":0.59999999999999998,"planeBounds":{"left":-0.046153846153846052,"bottom":0.41430769230769238,"right":0.64615384615384619,"top":1.0296923076923077},"atlasBounds":{"left":337.5,"bottom":56.5,"right":346.5,"top":64.5}},{"unicode":731,"advance":0.59999999999999998,"planeBounds":{"left":-0.022692307692307616,"bottom":-0.40419230769230768,"right":0.59269230769230774,"top":0.21119230769230768},"atlasBounds":{"left":328.5,"bottom":56.5,"right":336.5,"top":64.5}},{"unicode":732,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384565,"bottom":0.44576923076923086,"right":0.68461538461538463,"top":0.98423076923076935},"atlasBounds":{"left":472.5,"bottom":57.5,"right":482.5,"top":64.5}},{"unicode":733,"advance":0.59999999999999998,"planeBounds":{"left":-0.047115384615384524,"bottom":0.44576923076923086,"right":0.7221153846153846,"top":0.98423076923076935},"atlasBounds":{"left":11.5,"bottom":47.5,"right":21.5,"top":54.5}},{"unicode":759,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384565,"bottom":-0.41423076923076918,"right":0.68461538461538463,"top":0.12423076923076928},"atlasBounds":{"left":42.5,"bottom":47.5,"right":52.5,"top":54.5}},{"unicode":768,"advance":0,"planeBounds":{"left":-0.64069230769230778,"bottom":0.44576923076923086,"right":-0.025307692307692378,"top":0.98423076923076935},"atlasBounds":{"left":65.5,"bottom":47.5,"right":73.5,"top":54.5}},{"unicode":769,"advance":0,"planeBounds":{"left":-0.57419230769230767,"bottom":0.44576923076923086,"right":0.041192307692307736,"top":0.98423076923076935},"atlasBounds":{"left":123.5,"bottom":47.5,"right":131.5,"top":54.5}},{"unicode":770,"advance":0,"planeBounds":{"left":-0.68461538461538451,"bottom":0.44576923076923086,"right":0.084615384615384648,"top":0.98423076923076935},"atlasBounds":{"left":162.5,"bottom":47.5,"right":172.5,"top":54.5}},{"unicode":771,"advance":0,"planeBounds":{"left":-0.68461538461538463,"bottom":0.44576923076923086,"right":0.084615384615384523,"top":0.98423076923076935},"atlasBounds":{"left":204.5,"bottom":47.5,"right":214.5,"top":54.5}},{"unicode":772,"advance":0,"planeBounds":{"left":-0.68461538461538463,"bottom":0.47673076923076929,"right":0.084615384615384523,"top":0.93826923076923086},"atlasBounds":{"left":289.5,"bottom":48.5,"right":299.5,"top":54.5}},{"unicode":774,"advance":0,"planeBounds":{"left":-0.68461538461538463,"bottom":0.44326923076923086,"right":0.084615384615384523,"top":0.9817307692307693},"atlasBounds":{"left":112.5,"bottom":47.5,"right":122.5,"top":54.5}},{"unicode":775,"advance":0,"planeBounds":{"left":-0.56923076923076921,"bottom":0.44376923076923086,"right":-0.030769230769230719,"top":0.98223076923076935},"atlasBounds":{"left":464.5,"bottom":57.5,"right":471.5,"top":64.5}},{"unicode":776,"advance":0,"planeBounds":{"left":-0.68461538461538463,"bottom":0.44376923076923086,"right":0.084615384615384523,"top":0.98223076923076935},"atlasBounds":{"left":22.5,"bottom":47.5,"right":32.5,"top":54.5}},{"unicode":777,"advance":0,"planeBounds":{"left":-0.60269230769230764,"bottom":0.42730769230769239,"right":0.012692307692307737,"top":1.0426923076923076},"atlasBounds":{"left":154.5,"bottom":56.5,"right":162.5,"top":64.5}},{"unicode":778,"advance":0,"planeBounds":{"left":-0.64615384615384608,"bottom":0.41430769230769238,"right":0.046153846153846191,"top":1.0296923076923077},"atlasBounds":{"left":71.5,"bottom":56.5,"right":80.5,"top":64.5}},{"unicode":779,"advance":0,"planeBounds":{"left":-0.64711538461538454,"bottom":0.44576923076923086,"right":0.12211538461538464,"top":0.98423076923076935},"atlasBounds":{"left":347.5,"bottom":57.5,"right":357.5,"top":64.5}},{"unicode":780,"advance":0,"planeBounds":{"left":-0.68461538461538451,"bottom":0.44576923076923086,"right":0.084615384615384648,"top":0.98423076923076935},"atlasBounds":{"left":384.5,"bottom":57.5,"right":394.5,"top":64.5}},{"unicode":783,"advance":0,"planeBounds":{"left":-0.72211538461538449,"bottom":0.44576923076923086,"right":0.047115384615384642,"top":0.98423076923076935},"atlasBounds":{"left":427.5,"bottom":57.5,"right":437.5,"top":64.5}},{"unicode":786,"advance":0,"planeBounds":{"left":-0.59519230769230769,"bottom":0.41634615384615392,"right":0.020192307692307735,"top":1.1086538461538462},"atlasBounds":{"left":317.5,"bottom":65.5,"right":325.5,"top":74.5}},{"unicode":795,"advance":0,"planeBounds":{"left":-0.43323076923076925,"bottom":0.26480769230769241,"right":0.10523076923076922,"top":0.88019230769230772},"atlasBounds":{"left":205.5,"bottom":56.5,"right":212.5,"top":64.5}},{"unicode":803,"advance":0,"planeBounds":{"left":-0.56923076923076921,"bottom":-0.41823076923076924,"right":-0.030769230769230719,"top":0.12023076923076922},"atlasBounds":{"left":143.5,"bottom":47.5,"right":150.5,"top":54.5}},{"unicode":805,"advance":0,"planeBounds":{"left":-0.64615384615384608,"bottom":-0.45769230769230768,"right":0.046153846153846191,"top":0.15769230769230769},"atlasBounds":{"left":81.5,"bottom":56.5,"right":90.5,"top":64.5}},{"unicode":806,"advance":0,"planeBounds":{"left":-0.61769230769230776,"bottom":-0.43673076923076926,"right":-0.0023076923076923777,"top":0.10173076923076922},"atlasBounds":{"left":195.5,"bottom":47.5,"right":203.5,"top":54.5}},{"unicode":807,"advance":0,"planeBounds":{"left":-0.60019230769230758,"bottom":-0.39619230769230768,"right":0.015192307692307736,"top":0.21919230769230769},"atlasBounds":{"left":112.5,"bottom":56.5,"right":120.5,"top":64.5}},{"unicode":808,"advance":0,"planeBounds":{"left":-0.62269230769230777,"bottom":-0.40419230769230768,"right":-0.0073076923076923778,"top":0.21119230769230768},"atlasBounds":{"left":121.5,"bottom":56.5,"right":129.5,"top":64.5}},{"unicode":822,"advance":0,"planeBounds":{"left":-0.83846153846153848,"bottom":0.094230769230769285,"right":0.23846153846153834,"top":0.55576923076923079},"atlasBounds":{"left":274.5,"bottom":48.5,"right":288.5,"top":54.5}},{"unicode":823,"advance":0,"planeBounds":{"left":-0.76153846153846161,"bottom":-0.25846153846153841,"right":0.16153846153846155,"top":0.81846153846153835},"atlasBounds":{"left":446.5,"bottom":143.5,"right":458.5,"top":157.5}},{"unicode":824,"advance":0,"planeBounds":{"left":-0.68461538461538451,"bottom":-0.34038461538461523,"right":0.084615384615384648,"top":0.89038461538461555},"atlasBounds":{"left":110.5,"bottom":255.5,"right":120.5,"top":271.5}},{"unicode":884,"advance":0.59999999999999998,"planeBounds":{"left":-0.037692307692307615,"bottom":0.18088461538461548,"right":0.57769230769230773,"top":0.95011538461538469},"atlasBounds":{"left":303.5,"bottom":76.5,"right":311.5,"top":86.5}},{"unicode":885,"advance":0.59999999999999998,"planeBounds":{"left":-0.037692307692307615,"bottom":-0.38911538461538459,"right":0.57769230769230773,"top":0.38011538461538458},"atlasBounds":{"left":312.5,"bottom":76.5,"right":320.5,"top":86.5}},{"unicode":894,"advance":0.59999999999999998,"planeBounds":{"left":-0.030192307692307602,"bottom":-0.37692307692307686,"right":0.58519230769230779,"top":0.77692307692307694},"atlasBounds":{"left":503.5,"bottom":239.5,"right":511.5,"top":254.5}},{"unicode":900,"advance":0.59999999999999998,"planeBounds":{"left":0.005807692307692406,"bottom":0.44576923076923086,"right":0.62119230769230771,"top":0.98423076923076935},"atlasBounds":{"left":74.5,"bottom":47.5,"right":82.5,"top":54.5}},{"unicode":901,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384523,"bottom":0.42834615384615393,"right":0.68461538461538463,"top":1.1206538461538462},"atlasBounds":{"left":212.5,"bottom":65.5,"right":222.5,"top":74.5}},{"unicode":902,"advance":0.59999999999999998,"planeBounds":{"left":-0.21999999999999989,"bottom":-0.2119230769230768,"right":0.78000000000000003,"top":0.94192307692307697},"atlasBounds":{"left":71.5,"bottom":158.5,"right":84.5,"top":173.5}},{"unicode":903,"advance":0.59999999999999998,"planeBounds":{"left":-0.0076923076923076155,"bottom":0.040307692307692385,"right":0.60769230769230775,"top":0.6556923076923078},"atlasBounds":{"left":145.5,"bottom":56.5,"right":153.5,"top":64.5}},{"unicode":904,"advance":0.59999999999999998,"planeBounds":{"left":-0.33596153846153842,"bottom":-0.2119230769230768,"right":0.74096153846153834,"top":0.94192307692307697},"atlasBounds":{"left":43.5,"bottom":158.5,"right":57.5,"top":173.5}},{"unicode":905,"advance":0.59999999999999998,"planeBounds":{"left":-0.34246153846153837,"bottom":-0.2119230769230768,"right":0.7344615384615385,"top":0.94192307692307697},"atlasBounds":{"left":28.5,"bottom":158.5,"right":42.5,"top":173.5}},{"unicode":906,"advance":0.59999999999999998,"planeBounds":{"left":-0.30999999999999989,"bottom":-0.2119230769230768,"right":0.69000000000000006,"top":0.94192307692307697},"atlasBounds":{"left":14.5,"bottom":158.5,"right":27.5,"top":173.5}},{"unicode":908,"advance":0.59999999999999998,"planeBounds":{"left":-0.28399999999999992,"bottom":-0.2119230769230768,"right":0.71599999999999997,"top":0.94192307692307697},"atlasBounds":{"left":0.5,"bottom":158.5,"right":13.5,"top":173.5}},{"unicode":910,"advance":0.59999999999999998,"planeBounds":{"left":-0.31846153846153841,"bottom":-0.2119230769230768,"right":0.75846153846153841,"top":0.94192307692307697},"atlasBounds":{"left":493.5,"bottom":174.5,"right":507.5,"top":189.5}},{"unicode":911,"advance":0.59999999999999998,"planeBounds":{"left":-0.30296153846153834,"bottom":-0.20692307692307685,"right":0.77396153846153848,"top":0.94692307692307698},"atlasBounds":{"left":478.5,"bottom":174.5,"right":492.5,"top":189.5}},{"unicode":912,"advance":0.59999999999999998,"planeBounds":{"left":-0.15903846153846146,"bottom":-0.20384615384615376,"right":0.76403846153846167,"top":1.1038461538461541},"atlasBounds":{"left":403.5,"bottom":324.5,"right":415.5,"top":341.5}},{"unicode":913,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":450.5,"bottom":174.5,"right":462.5,"top":189.5}},{"unicode":914,"advance":0.59999999999999998,"planeBounds":{"left":-0.11157692307692298,"bottom":-0.2119230769230768,"right":0.73457692307692313,"top":0.94192307692307697},"atlasBounds":{"left":438.5,"bottom":174.5,"right":449.5,"top":189.5}},{"unicode":915,"advance":0.59999999999999998,"planeBounds":{"left":-0.080576923076923004,"bottom":-0.2119230769230768,"right":0.76557692307692315,"top":0.94192307692307697},"atlasBounds":{"left":426.5,"bottom":174.5,"right":437.5,"top":189.5}},{"unicode":916,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":413.5,"bottom":174.5,"right":425.5,"top":189.5}},{"unicode":917,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692298,"bottom":-0.2119230769230768,"right":0.73307692307692307,"top":0.94192307692307697},"atlasBounds":{"left":401.5,"bottom":174.5,"right":412.5,"top":189.5}},{"unicode":918,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":389.5,"bottom":174.5,"right":400.5,"top":189.5}},{"unicode":919,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":377.5,"bottom":174.5,"right":388.5,"top":189.5}},{"unicode":920,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":365.5,"bottom":174.5,"right":376.5,"top":189.5}},{"unicode":921,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":353.5,"bottom":174.5,"right":364.5,"top":189.5}},{"unicode":922,"advance":0.59999999999999998,"planeBounds":{"left":-0.13553846153846144,"bottom":-0.2119230769230768,"right":0.78753846153846163,"top":0.94192307692307697},"atlasBounds":{"left":340.5,"bottom":174.5,"right":352.5,"top":189.5}},{"unicode":923,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":327.5,"bottom":174.5,"right":339.5,"top":189.5}},{"unicode":924,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":315.5,"bottom":174.5,"right":326.5,"top":189.5}},{"unicode":925,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":303.5,"bottom":174.5,"right":314.5,"top":189.5}},{"unicode":926,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":291.5,"bottom":174.5,"right":302.5,"top":189.5}},{"unicode":927,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":279.5,"bottom":174.5,"right":290.5,"top":189.5}},{"unicode":928,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":267.5,"bottom":174.5,"right":278.5,"top":189.5}},{"unicode":929,"advance":0.59999999999999998,"planeBounds":{"left":-0.10207692307692301,"bottom":-0.2119230769230768,"right":0.74407692307692308,"top":0.94192307692307697},"atlasBounds":{"left":255.5,"bottom":174.5,"right":266.5,"top":189.5}},{"unicode":931,"advance":0.59999999999999998,"planeBounds":{"left":-0.14903846153846145,"bottom":-0.2119230769230768,"right":0.77403846153846168,"top":0.94192307692307697},"atlasBounds":{"left":242.5,"bottom":174.5,"right":254.5,"top":189.5}},{"unicode":932,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":229.5,"bottom":174.5,"right":241.5,"top":189.5}},{"unicode":933,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":216.5,"bottom":174.5,"right":228.5,"top":189.5}},{"unicode":934,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.24288461538461531,"right":0.76153846153846172,"top":0.98788461538461547},"atlasBounds":{"left":144.5,"bottom":255.5,"right":156.5,"top":271.5}},{"unicode":935,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":190.5,"bottom":174.5,"right":202.5,"top":189.5}},{"unicode":936,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":177.5,"bottom":174.5,"right":189.5,"top":189.5}},{"unicode":937,"advance":0.59999999999999998,"planeBounds":{"left":-0.16103846153846144,"bottom":-0.20692307692307685,"right":0.76203846153846166,"top":0.94692307692307698},"atlasBounds":{"left":164.5,"bottom":174.5,"right":176.5,"top":189.5}},{"unicode":938,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.21980769230769215,"right":0.72307692307692306,"top":1.1648076923076924},"atlasBounds":{"left":64.5,"bottom":361.5,"right":75.5,"top":379.5}},{"unicode":939,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.21980769230769215,"right":0.76153846153846172,"top":1.1648076923076924},"atlasBounds":{"left":76.5,"bottom":361.5,"right":88.5,"top":379.5}},{"unicode":940,"advance":0.59999999999999998,"planeBounds":{"left":-0.12507692307692297,"bottom":-0.2278846153846153,"right":0.72107692307692306,"top":1.0028846153846156},"atlasBounds":{"left":169.5,"bottom":255.5,"right":180.5,"top":271.5}},{"unicode":941,"advance":0.59999999999999998,"planeBounds":{"left":-0.11557692307692298,"bottom":-0.2263846153846153,"right":0.73057692307692312,"top":1.0043846153846154},"atlasBounds":{"left":181.5,"bottom":255.5,"right":192.5,"top":271.5}},{"unicode":942,"advance":0.59999999999999998,"planeBounds":{"left":-0.12207692307692299,"bottom":-0.3898076923076923,"right":0.72407692307692306,"top":0.99480769230769239},"atlasBounds":{"left":89.5,"bottom":361.5,"right":100.5,"top":379.5}},{"unicode":943,"advance":0.59999999999999998,"planeBounds":{"left":-0.15903846153846146,"bottom":-0.22288461538461524,"right":0.76403846153846167,"top":1.0078846153846157},"atlasBounds":{"left":193.5,"bottom":255.5,"right":205.5,"top":271.5}},{"unicode":944,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.20884615384615379,"right":0.72307692307692306,"top":1.098846153846154},"atlasBounds":{"left":416.5,"bottom":324.5,"right":427.5,"top":341.5}},{"unicode":945,"advance":0.59999999999999998,"planeBounds":{"left":-0.12507692307692297,"bottom":-0.22499999999999989,"right":0.72107692307692306,"top":0.77500000000000002},"atlasBounds":{"left":377.5,"bottom":100.5,"right":388.5,"top":113.5}},{"unicode":946,"advance":0.59999999999999998,"planeBounds":{"left":-0.11207692307692298,"bottom":-0.37384615384615372,"right":0.73407692307692307,"top":0.9338461538461541},"atlasBounds":{"left":428.5,"bottom":324.5,"right":439.5,"top":341.5}},{"unicode":947,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":-0.39192307692307682,"right":0.76153846153846172,"top":0.76192307692307693},"atlasBounds":{"left":37.5,"bottom":174.5,"right":49.5,"top":189.5}},{"unicode":948,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.21692307692307686,"right":0.72307692307692306,"top":0.93692307692307697},"atlasBounds":{"left":25.5,"bottom":174.5,"right":36.5,"top":189.5}},{"unicode":949,"advance":0.59999999999999998,"planeBounds":{"left":-0.11557692307692298,"bottom":-0.22499999999999989,"right":0.73057692307692312,"top":0.77500000000000002},"atlasBounds":{"left":365.5,"bottom":100.5,"right":376.5,"top":113.5}},{"unicode":950,"advance":0.59999999999999998,"planeBounds":{"left":-0.16003846153846144,"bottom":-0.37884615384615383,"right":0.76303846153846167,"top":0.92884615384615399},"atlasBounds":{"left":440.5,"bottom":324.5,"right":452.5,"top":341.5}},{"unicode":951,"advance":0.59999999999999998,"planeBounds":{"left":-0.12207692307692299,"bottom":-0.38692307692307687,"right":0.72407692307692306,"top":0.76692307692307693},"atlasBounds":{"left":0.5,"bottom":174.5,"right":11.5,"top":189.5}},{"unicode":952,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.21692307692307686,"right":0.72307692307692306,"top":0.93692307692307697},"atlasBounds":{"left":486.5,"bottom":190.5,"right":497.5,"top":205.5}},{"unicode":953,"advance":0.59999999999999998,"planeBounds":{"left":-0.15903846153846146,"bottom":-0.22499999999999995,"right":0.76403846153846167,"top":0.77500000000000002},"atlasBounds":{"left":352.5,"bottom":100.5,"right":364.5,"top":113.5}},{"unicode":954,"advance":0.59999999999999998,"planeBounds":{"left":-0.1355384615384615,"bottom":-0.22499999999999995,"right":0.78753846153846163,"top":0.77500000000000002},"atlasBounds":{"left":326.5,"bottom":100.5,"right":338.5,"top":113.5}},{"unicode":955,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":447.5,"bottom":190.5,"right":459.5,"top":205.5}},{"unicode":956,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.39192307692307682,"right":0.72307692307692306,"top":0.76192307692307693},"atlasBounds":{"left":435.5,"bottom":190.5,"right":446.5,"top":205.5}},{"unicode":957,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.22499999999999995,"right":0.76153846153846172,"top":0.77500000000000002},"atlasBounds":{"left":313.5,"bottom":100.5,"right":325.5,"top":113.5}},{"unicode":958,"advance":0.59999999999999998,"planeBounds":{"left":-0.1365384615384615,"bottom":-0.37784615384615383,"right":0.78653846153846163,"top":0.92984615384615399},"atlasBounds":{"left":453.5,"bottom":324.5,"right":465.5,"top":341.5}},{"unicode":959,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.22499999999999995,"right":0.72307692307692306,"top":0.77500000000000002},"atlasBounds":{"left":301.5,"bottom":100.5,"right":312.5,"top":113.5}},{"unicode":960,"advance":0.59999999999999998,"planeBounds":{"left":-0.17153846153846147,"bottom":-0.2274999999999999,"right":0.75153846153846171,"top":0.77249999999999996},"atlasBounds":{"left":288.5,"bottom":100.5,"right":300.5,"top":113.5}},{"unicode":961,"advance":0.59999999999999998,"planeBounds":{"left":-0.12107692307692298,"bottom":-0.38692307692307687,"right":0.72507692307692306,"top":0.76692307692307693},"atlasBounds":{"left":374.5,"bottom":190.5,"right":385.5,"top":205.5}},{"unicode":962,"advance":0.59999999999999998,"planeBounds":{"left":-0.11057692307692299,"bottom":-0.38692307692307687,"right":0.73557692307692313,"top":0.76692307692307693},"atlasBounds":{"left":362.5,"bottom":190.5,"right":373.5,"top":205.5}},{"unicode":963,"advance":0.59999999999999998,"planeBounds":{"left":-0.13403846153846144,"bottom":-0.2299999999999999,"right":0.78903846153846169,"top":0.77000000000000002},"atlasBounds":{"left":275.5,"bottom":100.5,"right":287.5,"top":113.5}},{"unicode":964,"advance":0.59999999999999998,"planeBounds":{"left":-0.16903846153846144,"bottom":-0.22499999999999995,"right":0.75403846153846166,"top":0.77500000000000002},"atlasBounds":{"left":250.5,"bottom":100.5,"right":262.5,"top":113.5}},{"unicode":965,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2299999999999999,"right":0.72307692307692306,"top":0.77000000000000002},"atlasBounds":{"left":238.5,"bottom":100.5,"right":249.5,"top":113.5}},{"unicode":966,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.39192307692307682,"right":0.76153846153846172,"top":0.76192307692307693},"atlasBounds":{"left":313.5,"bottom":190.5,"right":325.5,"top":205.5}},{"unicode":967,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.39192307692307682,"right":0.76153846153846172,"top":0.76192307692307693},"atlasBounds":{"left":300.5,"bottom":190.5,"right":312.5,"top":205.5}},{"unicode":968,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.39192307692307682,"right":0.76153846153846172,"top":0.76192307692307693},"atlasBounds":{"left":287.5,"bottom":190.5,"right":299.5,"top":205.5}},{"unicode":969,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.22499999999999989,"right":0.76153846153846172,"top":0.77500000000000002},"atlasBounds":{"left":225.5,"bottom":100.5,"right":237.5,"top":113.5}},{"unicode":970,"advance":0.59999999999999998,"planeBounds":{"left":-0.15903846153846146,"bottom":-0.2268846153846153,"right":0.76403846153846167,"top":1.0038846153846155},"atlasBounds":{"left":219.5,"bottom":255.5,"right":231.5,"top":271.5}},{"unicode":971,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.23188461538461524,"right":0.72307692307692306,"top":0.99888461538461559},"atlasBounds":{"left":232.5,"bottom":255.5,"right":243.5,"top":271.5}},{"unicode":972,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.2268846153846153,"right":0.72307692307692306,"top":1.0038846153846155},"atlasBounds":{"left":244.5,"bottom":255.5,"right":255.5,"top":271.5}},{"unicode":973,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2278846153846153,"right":0.72307692307692306,"top":1.0028846153846156},"atlasBounds":{"left":256.5,"bottom":255.5,"right":267.5,"top":271.5}},{"unicode":974,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2278846153846153,"right":0.76153846153846172,"top":1.0028846153846156},"atlasBounds":{"left":279.5,"bottom":255.5,"right":291.5,"top":271.5}},{"unicode":975,"advance":0.59999999999999998,"planeBounds":{"left":-0.12553846153846143,"bottom":-0.37884615384615383,"right":0.79753846153846164,"top":0.92884615384615399},"atlasBounds":{"left":0.5,"bottom":306.5,"right":12.5,"top":323.5}},{"unicode":981,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.37884615384615383,"right":0.76153846153846172,"top":0.92884615384615399},"atlasBounds":{"left":13.5,"bottom":306.5,"right":25.5,"top":323.5}},{"unicode":982,"advance":0.59999999999999998,"planeBounds":{"left":-0.1999999999999999,"bottom":-0.2299999999999999,"right":0.80000000000000004,"top":0.77000000000000002},"atlasBounds":{"left":157.5,"bottom":100.5,"right":170.5,"top":113.5}},{"unicode":983,"advance":0.59999999999999998,"planeBounds":{"left":-0.10207692307692301,"bottom":-0.39192307692307682,"right":0.74407692307692308,"top":0.76192307692307693},"atlasBounds":{"left":162.5,"bottom":190.5,"right":173.5,"top":205.5}},{"unicode":1025,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692298,"bottom":-0.21980769230769215,"right":0.73307692307692307,"top":1.1648076923076924},"atlasBounds":{"left":101.5,"bottom":361.5,"right":112.5,"top":379.5}},{"unicode":1026,"advance":0.59999999999999998,"planeBounds":{"left":-0.19403846153846144,"bottom":-0.37884615384615383,"right":0.72903846153846164,"top":0.92884615384615399},"atlasBounds":{"left":26.5,"bottom":306.5,"right":38.5,"top":323.5}},{"unicode":1027,"advance":0.59999999999999998,"planeBounds":{"left":-0.080576923076923004,"bottom":-0.21730769230769215,"right":0.76557692307692315,"top":1.1673076923076924},"atlasBounds":{"left":113.5,"bottom":361.5,"right":124.5,"top":379.5}},{"unicode":1028,"advance":0.59999999999999998,"planeBounds":{"left":-0.12007692307692301,"bottom":-0.2119230769230768,"right":0.72607692307692306,"top":0.94192307692307697},"atlasBounds":{"left":113.5,"bottom":190.5,"right":124.5,"top":205.5}},{"unicode":1029,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":101.5,"bottom":190.5,"right":112.5,"top":205.5}},{"unicode":1030,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":89.5,"bottom":190.5,"right":100.5,"top":205.5}},{"unicode":1031,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.21980769230769215,"right":0.72307692307692306,"top":1.1648076923076924},"atlasBounds":{"left":125.5,"bottom":361.5,"right":136.5,"top":379.5}},{"unicode":1032,"advance":0.59999999999999998,"planeBounds":{"left":-0.15307692307692297,"bottom":-0.21692307692307686,"right":0.69307692307692315,"top":0.93692307692307697},"atlasBounds":{"left":64.5,"bottom":190.5,"right":75.5,"top":205.5}},{"unicode":1033,"advance":0.59999999999999998,"planeBounds":{"left":-0.21249999999999994,"bottom":-0.21442307692307683,"right":0.78749999999999998,"top":0.93942307692307692},"atlasBounds":{"left":50.5,"bottom":190.5,"right":63.5,"top":205.5}},{"unicode":1034,"advance":0.59999999999999998,"planeBounds":{"left":-0.14503846153846148,"bottom":-0.2119230769230768,"right":0.77803846153846168,"top":0.94192307692307697},"atlasBounds":{"left":37.5,"bottom":190.5,"right":49.5,"top":205.5}},{"unicode":1035,"advance":0.59999999999999998,"planeBounds":{"left":-0.19403846153846144,"bottom":-0.2119230769230768,"right":0.72903846153846164,"top":0.94192307692307697},"atlasBounds":{"left":24.5,"bottom":190.5,"right":36.5,"top":205.5}},{"unicode":1036,"advance":0.59999999999999998,"planeBounds":{"left":-0.13553846153846144,"bottom":-0.21730769230769215,"right":0.78753846153846163,"top":1.1673076923076924},"atlasBounds":{"left":137.5,"bottom":361.5,"right":149.5,"top":379.5}},{"unicode":1038,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.21730769230769215,"right":0.76153846153846172,"top":1.1673076923076924},"atlasBounds":{"left":150.5,"bottom":361.5,"right":162.5,"top":379.5}},{"unicode":1039,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.35134615384615381,"right":0.72307692307692306,"top":0.95634615384615396},"atlasBounds":{"left":39.5,"bottom":306.5,"right":50.5,"top":323.5}},{"unicode":1040,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":491.5,"bottom":206.5,"right":503.5,"top":221.5}},{"unicode":1041,"advance":0.59999999999999998,"planeBounds":{"left":-0.11107692307692299,"bottom":-0.2119230769230768,"right":0.73507692307692307,"top":0.94192307692307697},"atlasBounds":{"left":479.5,"bottom":206.5,"right":490.5,"top":221.5}},{"unicode":1042,"advance":0.59999999999999998,"planeBounds":{"left":-0.11157692307692298,"bottom":-0.2119230769230768,"right":0.73457692307692313,"top":0.94192307692307697},"atlasBounds":{"left":467.5,"bottom":206.5,"right":478.5,"top":221.5}},{"unicode":1043,"advance":0.59999999999999998,"planeBounds":{"left":-0.080576923076923004,"bottom":-0.2119230769230768,"right":0.76557692307692315,"top":0.94192307692307697},"atlasBounds":{"left":455.5,"bottom":206.5,"right":466.5,"top":221.5}},{"unicode":1044,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999996,"bottom":-0.35884615384615376,"right":0.80000000000000004,"top":0.94884615384615401},"atlasBounds":{"left":51.5,"bottom":306.5,"right":64.5,"top":323.5}},{"unicode":1045,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692298,"bottom":-0.2119230769230768,"right":0.73307692307692307,"top":0.94192307692307697},"atlasBounds":{"left":428.5,"bottom":206.5,"right":439.5,"top":221.5}},{"unicode":1046,"advance":0.59999999999999998,"planeBounds":{"left":-0.1999999999999999,"bottom":-0.2119230769230768,"right":0.80000000000000004,"top":0.94192307692307697},"atlasBounds":{"left":414.5,"bottom":206.5,"right":427.5,"top":221.5}},{"unicode":1047,"advance":0.59999999999999998,"planeBounds":{"left":-0.16953846153846147,"bottom":-0.2119230769230768,"right":0.75353846153846171,"top":0.94192307692307697},"atlasBounds":{"left":401.5,"bottom":206.5,"right":413.5,"top":221.5}},{"unicode":1048,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":389.5,"bottom":206.5,"right":400.5,"top":221.5}},{"unicode":1049,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.21730769230769215,"right":0.72307692307692306,"top":1.1673076923076924},"atlasBounds":{"left":163.5,"bottom":361.5,"right":174.5,"top":379.5}},{"unicode":1050,"advance":0.59999999999999998,"planeBounds":{"left":-0.13553846153846144,"bottom":-0.2119230769230768,"right":0.78753846153846163,"top":0.94192307692307697},"atlasBounds":{"left":366.5,"bottom":206.5,"right":378.5,"top":221.5}},{"unicode":1051,"advance":0.59999999999999998,"planeBounds":{"left":-0.19853846153846144,"bottom":-0.21442307692307683,"right":0.72453846153846169,"top":0.93942307692307692},"atlasBounds":{"left":353.5,"bottom":206.5,"right":365.5,"top":221.5}},{"unicode":1052,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":341.5,"bottom":206.5,"right":352.5,"top":221.5}},{"unicode":1053,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":329.5,"bottom":206.5,"right":340.5,"top":221.5}},{"unicode":1054,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":317.5,"bottom":206.5,"right":328.5,"top":221.5}},{"unicode":1055,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":305.5,"bottom":206.5,"right":316.5,"top":221.5}},{"unicode":1056,"advance":0.59999999999999998,"planeBounds":{"left":-0.10207692307692301,"bottom":-0.2119230769230768,"right":0.74407692307692308,"top":0.94192307692307697},"atlasBounds":{"left":293.5,"bottom":206.5,"right":304.5,"top":221.5}},{"unicode":1057,"advance":0.59999999999999998,"planeBounds":{"left":-0.12007692307692296,"bottom":-0.2119230769230768,"right":0.72607692307692306,"top":0.94192307692307697},"atlasBounds":{"left":281.5,"bottom":206.5,"right":292.5,"top":221.5}},{"unicode":1058,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":268.5,"bottom":206.5,"right":280.5,"top":221.5}},{"unicode":1059,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":255.5,"bottom":206.5,"right":267.5,"top":221.5}},{"unicode":1060,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.24288461538461531,"right":0.76153846153846172,"top":0.98788461538461547},"atlasBounds":{"left":329.5,"bottom":255.5,"right":341.5,"top":271.5}},{"unicode":1061,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":229.5,"bottom":206.5,"right":241.5,"top":221.5}},{"unicode":1062,"advance":0.59999999999999998,"planeBounds":{"left":-0.13203846153846144,"bottom":-0.35884615384615376,"right":0.79103846153846169,"top":0.94884615384615401},"atlasBounds":{"left":65.5,"bottom":306.5,"right":77.5,"top":323.5}},{"unicode":1063,"advance":0.59999999999999998,"planeBounds":{"left":-0.13307692307692295,"bottom":-0.2119230769230768,"right":0.71307692307692316,"top":0.94192307692307697},"atlasBounds":{"left":205.5,"bottom":206.5,"right":216.5,"top":221.5}},{"unicode":1064,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":192.5,"bottom":206.5,"right":204.5,"top":221.5}},{"unicode":1065,"advance":0.59999999999999998,"planeBounds":{"left":-0.14203846153846147,"bottom":-0.35884615384615376,"right":0.78103846153846168,"top":0.94884615384615401},"atlasBounds":{"left":78.5,"bottom":306.5,"right":90.5,"top":323.5}},{"unicode":1066,"advance":0.59999999999999998,"planeBounds":{"left":-0.2274999999999999,"bottom":-0.2119230769230768,"right":0.77249999999999996,"top":0.94192307692307697},"atlasBounds":{"left":166.5,"bottom":206.5,"right":179.5,"top":221.5}},{"unicode":1067,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":153.5,"bottom":206.5,"right":165.5,"top":221.5}},{"unicode":1068,"advance":0.59999999999999998,"planeBounds":{"left":-0.10107692307692298,"bottom":-0.2119230769230768,"right":0.74507692307692308,"top":0.94192307692307697},"atlasBounds":{"left":141.5,"bottom":206.5,"right":152.5,"top":221.5}},{"unicode":1069,"advance":0.59999999999999998,"planeBounds":{"left":-0.126076923076923,"bottom":-0.2119230769230768,"right":0.72007692307692306,"top":0.94192307692307697},"atlasBounds":{"left":129.5,"bottom":206.5,"right":140.5,"top":221.5}},{"unicode":1070,"advance":0.59999999999999998,"planeBounds":{"left":-0.15153846153846143,"bottom":-0.2119230769230768,"right":0.77153846153846173,"top":0.94192307692307697},"atlasBounds":{"left":116.5,"bottom":206.5,"right":128.5,"top":221.5}},{"unicode":1071,"advance":0.59999999999999998,"planeBounds":{"left":-0.14257692307692299,"bottom":-0.2119230769230768,"right":0.7035769230769231,"top":0.94192307692307697},"atlasBounds":{"left":104.5,"bottom":206.5,"right":115.5,"top":221.5}},{"unicode":1072,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.22499999999999989,"right":0.71057692307692311,"top":0.77500000000000002},"atlasBounds":{"left":470.5,"bottom":114.5,"right":481.5,"top":127.5}},{"unicode":1073,"advance":0.59999999999999998,"planeBounds":{"left":-0.12107692307692296,"bottom":-0.21692307692307686,"right":0.72507692307692306,"top":0.93692307692307697},"atlasBounds":{"left":78.5,"bottom":206.5,"right":89.5,"top":221.5}},{"unicode":1074,"advance":0.59999999999999998,"planeBounds":{"left":-0.11457692307692298,"bottom":-0.22499999999999995,"right":0.73157692307692312,"top":0.77500000000000002},"atlasBounds":{"left":445.5,"bottom":114.5,"right":456.5,"top":127.5}},{"unicode":1075,"advance":0.59999999999999998,"planeBounds":{"left":-0.095576923076922976,"bottom":-0.22499999999999995,"right":0.75057692307692314,"top":0.77500000000000002},"atlasBounds":{"left":433.5,"bottom":114.5,"right":444.5,"top":127.5}},{"unicode":1076,"advance":0.59999999999999998,"planeBounds":{"left":-0.16403846153846144,"bottom":-0.33346153846153842,"right":0.75903846153846166,"top":0.7434615384615384},"atlasBounds":{"left":153.5,"bottom":143.5,"right":165.5,"top":157.5}},{"unicode":1077,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.22499999999999989,"right":0.72307692307692306,"top":0.77500000000000002},"atlasBounds":{"left":409.5,"bottom":114.5,"right":420.5,"top":127.5}},{"unicode":1078,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.22499999999999995,"right":0.80000000000000004,"top":0.77500000000000002},"atlasBounds":{"left":395.5,"bottom":114.5,"right":408.5,"top":127.5}},{"unicode":1079,"advance":0.59999999999999998,"planeBounds":{"left":-0.13057692307692298,"bottom":-0.22499999999999989,"right":0.71557692307692311,"top":0.77500000000000002},"atlasBounds":{"left":40.5,"bottom":100.5,"right":51.5,"top":113.5}},{"unicode":1080,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22499999999999995,"right":0.72307692307692306,"top":0.77500000000000002},"atlasBounds":{"left":383.5,"bottom":114.5,"right":394.5,"top":127.5}},{"unicode":1081,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22288461538461524,"right":0.72307692307692306,"top":1.0078846153846157},"atlasBounds":{"left":362.5,"bottom":255.5,"right":373.5,"top":271.5}},{"unicode":1082,"advance":0.59999999999999998,"planeBounds":{"left":-0.1355384615384615,"bottom":-0.22499999999999995,"right":0.78753846153846163,"top":0.77500000000000002},"atlasBounds":{"left":370.5,"bottom":114.5,"right":382.5,"top":127.5}},{"unicode":1083,"advance":0.59999999999999998,"planeBounds":{"left":-0.19503846153846144,"bottom":-0.2284999999999999,"right":0.72803846153846163,"top":0.77149999999999996},"atlasBounds":{"left":344.5,"bottom":114.5,"right":356.5,"top":127.5}},{"unicode":1084,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22499999999999995,"right":0.72307692307692306,"top":0.77500000000000002},"atlasBounds":{"left":332.5,"bottom":114.5,"right":343.5,"top":127.5}},{"unicode":1085,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22499999999999995,"right":0.72307692307692306,"top":0.77500000000000002},"atlasBounds":{"left":307.5,"bottom":114.5,"right":318.5,"top":127.5}},{"unicode":1086,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.22499999999999995,"right":0.72307692307692306,"top":0.77500000000000002},"atlasBounds":{"left":295.5,"bottom":114.5,"right":306.5,"top":127.5}},{"unicode":1087,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22499999999999995,"right":0.72307692307692306,"top":0.77500000000000002},"atlasBounds":{"left":230.5,"bottom":114.5,"right":241.5,"top":127.5}},{"unicode":1088,"advance":0.59999999999999998,"planeBounds":{"left":-0.11857692307692301,"bottom":-0.38692307692307687,"right":0.72757692307692312,"top":0.76692307692307693},"atlasBounds":{"left":402.5,"bottom":222.5,"right":413.5,"top":237.5}},{"unicode":1089,"advance":0.59999999999999998,"planeBounds":{"left":-0.12157692307692299,"bottom":-0.22499999999999989,"right":0.72457692307692312,"top":0.77500000000000002},"atlasBounds":{"left":153.5,"bottom":114.5,"right":164.5,"top":127.5}},{"unicode":1090,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.22499999999999995,"right":0.76153846153846172,"top":0.77500000000000002},"atlasBounds":{"left":103.5,"bottom":114.5,"right":115.5,"top":127.5}},{"unicode":1091,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.39192307692307682,"right":0.76153846153846172,"top":0.76192307692307693},"atlasBounds":{"left":363.5,"bottom":222.5,"right":375.5,"top":237.5}},{"unicode":1092,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.37884615384615383,"right":0.76153846153846172,"top":0.92884615384615399},"atlasBounds":{"left":91.5,"bottom":306.5,"right":103.5,"top":323.5}},{"unicode":1093,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.22499999999999995,"right":0.76153846153846172,"top":0.77500000000000002},"atlasBounds":{"left":490.5,"bottom":129.5,"right":502.5,"top":142.5}},{"unicode":1094,"advance":0.59999999999999998,"planeBounds":{"left":-0.13303846153846144,"bottom":-0.33346153846153842,"right":0.79003846153846169,"top":0.7434615384615384},"atlasBounds":{"left":75.5,"bottom":143.5,"right":87.5,"top":157.5}},{"unicode":1095,"advance":0.59999999999999998,"planeBounds":{"left":-0.13407692307692301,"bottom":-0.22499999999999995,"right":0.71207692307692316,"top":0.77500000000000002},"atlasBounds":{"left":466.5,"bottom":129.5,"right":477.5,"top":142.5}},{"unicode":1096,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":-0.22499999999999995,"right":0.76153846153846172,"top":0.77500000000000002},"atlasBounds":{"left":453.5,"bottom":129.5,"right":465.5,"top":142.5}},{"unicode":1097,"advance":0.59999999999999998,"planeBounds":{"left":-0.1440384615384615,"bottom":-0.33346153846153842,"right":0.77903846153846168,"top":0.7434615384615384},"atlasBounds":{"left":62.5,"bottom":143.5,"right":74.5,"top":157.5}},{"unicode":1098,"advance":0.59999999999999998,"planeBounds":{"left":-0.2274999999999999,"bottom":-0.22499999999999995,"right":0.77249999999999996,"top":0.77500000000000002},"atlasBounds":{"left":439.5,"bottom":129.5,"right":452.5,"top":142.5}},{"unicode":1099,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.22499999999999995,"right":0.76153846153846172,"top":0.77500000000000002},"atlasBounds":{"left":414.5,"bottom":129.5,"right":426.5,"top":142.5}},{"unicode":1100,"advance":0.59999999999999998,"planeBounds":{"left":-0.10157692307692295,"bottom":-0.22499999999999995,"right":0.74457692307692314,"top":0.77500000000000002},"atlasBounds":{"left":390.5,"bottom":129.5,"right":401.5,"top":142.5}},{"unicode":1101,"advance":0.59999999999999998,"planeBounds":{"left":-0.12257692307692299,"bottom":-0.22499999999999989,"right":0.72357692307692312,"top":0.77500000000000002},"atlasBounds":{"left":366.5,"bottom":129.5,"right":377.5,"top":142.5}},{"unicode":1102,"advance":0.59999999999999998,"planeBounds":{"left":-0.15153846153846143,"bottom":-0.22499999999999989,"right":0.77153846153846173,"top":0.77500000000000002},"atlasBounds":{"left":341.5,"bottom":129.5,"right":353.5,"top":142.5}},{"unicode":1103,"advance":0.59999999999999998,"planeBounds":{"left":-0.13657692307692301,"bottom":-0.22499999999999995,"right":0.70957692307692311,"top":0.77500000000000002},"atlasBounds":{"left":329.5,"bottom":129.5,"right":340.5,"top":142.5}},{"unicode":1105,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.23188461538461524,"right":0.72307692307692306,"top":0.99888461538461559},"atlasBounds":{"left":386.5,"bottom":255.5,"right":397.5,"top":271.5}},{"unicode":1106,"advance":0.59999999999999998,"planeBounds":{"left":-0.20753846153846145,"bottom":-0.37884615384615383,"right":0.71553846153846168,"top":0.92884615384615399},"atlasBounds":{"left":104.5,"bottom":306.5,"right":116.5,"top":323.5}},{"unicode":1107,"advance":0.59999999999999998,"planeBounds":{"left":-0.095576923076922976,"bottom":-0.22288461538461524,"right":0.75057692307692314,"top":1.0078846153846157},"atlasBounds":{"left":410.5,"bottom":255.5,"right":421.5,"top":271.5}},{"unicode":1108,"advance":0.59999999999999998,"planeBounds":{"left":-0.12357692307692299,"bottom":-0.22499999999999989,"right":0.72257692307692312,"top":0.77500000000000002},"atlasBounds":{"left":317.5,"bottom":129.5,"right":328.5,"top":142.5}},{"unicode":1109,"advance":0.59999999999999998,"planeBounds":{"left":-0.11807692307692295,"bottom":-0.22499999999999995,"right":0.72807692307692307,"top":0.77500000000000002},"atlasBounds":{"left":305.5,"bottom":129.5,"right":316.5,"top":142.5}},{"unicode":1110,"advance":0.59999999999999998,"planeBounds":{"left":-0.14153846153846145,"bottom":-0.2268846153846153,"right":0.78153846153846163,"top":1.0038846153846155},"atlasBounds":{"left":422.5,"bottom":255.5,"right":434.5,"top":271.5}},{"unicode":1111,"advance":0.59999999999999998,"planeBounds":{"left":-0.14153846153846145,"bottom":-0.2268846153846153,"right":0.78153846153846163,"top":1.0038846153846155},"atlasBounds":{"left":435.5,"bottom":255.5,"right":447.5,"top":271.5}},{"unicode":1112,"advance":0.59999999999999998,"planeBounds":{"left":-0.16007692307692298,"bottom":-0.39380769230769219,"right":0.68607692307692314,"top":0.99080769230769239},"atlasBounds":{"left":175.5,"bottom":361.5,"right":186.5,"top":379.5}},{"unicode":1113,"advance":0.59999999999999998,"planeBounds":{"left":-0.20849999999999988,"bottom":-0.2274999999999999,"right":0.79149999999999998,"top":0.77249999999999996},"atlasBounds":{"left":291.5,"bottom":129.5,"right":304.5,"top":142.5}},{"unicode":1114,"advance":0.59999999999999998,"planeBounds":{"left":-0.1415384615384615,"bottom":-0.22499999999999995,"right":0.78153846153846163,"top":0.77500000000000002},"atlasBounds":{"left":278.5,"bottom":129.5,"right":290.5,"top":142.5}},{"unicode":1115,"advance":0.59999999999999998,"planeBounds":{"left":-0.20653846153846148,"bottom":-0.2119230769230768,"right":0.71653846153846168,"top":0.94192307692307697},"atlasBounds":{"left":73.5,"bottom":222.5,"right":85.5,"top":237.5}},{"unicode":1116,"advance":0.59999999999999998,"planeBounds":{"left":-0.1355384615384615,"bottom":-0.22288461538461524,"right":0.78753846153846163,"top":1.0078846153846157},"atlasBounds":{"left":448.5,"bottom":255.5,"right":460.5,"top":271.5}},{"unicode":1118,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.3898076923076923,"right":0.76153846153846172,"top":0.99480769230769239},"atlasBounds":{"left":187.5,"bottom":361.5,"right":199.5,"top":379.5}},{"unicode":1119,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.32596153846153836,"right":0.72307692307692306,"top":0.75096153846153846},"atlasBounds":{"left":25.5,"bottom":143.5,"right":36.5,"top":157.5}},{"unicode":1168,"advance":0.59999999999999998,"planeBounds":{"left":-0.080576923076923004,"bottom":-0.20288461538461527,"right":0.76557692307692315,"top":1.0278846153846155},"atlasBounds":{"left":461.5,"bottom":255.5,"right":472.5,"top":271.5}},{"unicode":1169,"advance":0.59999999999999998,"planeBounds":{"left":-0.095576923076922976,"bottom":-0.21096153846153839,"right":0.75057692307692314,"top":0.86596153846153834},"atlasBounds":{"left":13.5,"bottom":143.5,"right":24.5,"top":157.5}},{"unicode":1170,"advance":0.59999999999999998,"planeBounds":{"left":-0.16903846153846144,"bottom":-0.2119230769230768,"right":0.75403846153846166,"top":0.94192307692307697},"atlasBounds":{"left":111.5,"bottom":158.5,"right":123.5,"top":173.5}},{"unicode":1171,"advance":0.59999999999999998,"planeBounds":{"left":-0.16403846153846144,"bottom":-0.22499999999999995,"right":0.75903846153846166,"top":0.77500000000000002},"atlasBounds":{"left":251.5,"bottom":129.5,"right":263.5,"top":142.5}},{"unicode":1178,"advance":0.59999999999999998,"planeBounds":{"left":-0.12853846153846143,"bottom":-0.35884615384615376,"right":0.79453846153846164,"top":0.94884615384615401},"atlasBounds":{"left":117.5,"bottom":306.5,"right":129.5,"top":323.5}},{"unicode":1179,"advance":0.59999999999999998,"planeBounds":{"left":-0.13053846153846149,"bottom":-0.33346153846153842,"right":0.79253846153846164,"top":0.7434615384615384},"atlasBounds":{"left":0.5,"bottom":143.5,"right":12.5,"top":157.5}},{"unicode":1186,"advance":0.59999999999999998,"planeBounds":{"left":-0.10357692307692301,"bottom":-0.35884615384615376,"right":0.74257692307692313,"top":0.94884615384615401},"atlasBounds":{"left":130.5,"bottom":306.5,"right":141.5,"top":323.5}},{"unicode":1187,"advance":0.59999999999999998,"planeBounds":{"left":-0.10257692307692301,"bottom":-0.33346153846153842,"right":0.74357692307692314,"top":0.7434615384615384},"atlasBounds":{"left":489.5,"bottom":159.5,"right":500.5,"top":173.5}},{"unicode":1198,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":440.5,"bottom":239.5,"right":452.5,"top":254.5}},{"unicode":1199,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":-0.39192307692307682,"right":0.76153846153846172,"top":0.76192307692307693},"atlasBounds":{"left":427.5,"bottom":239.5,"right":439.5,"top":254.5}},{"unicode":1200,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":414.5,"bottom":239.5,"right":426.5,"top":254.5}},{"unicode":1201,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":-0.39192307692307682,"right":0.76153846153846172,"top":0.76192307692307693},"atlasBounds":{"left":401.5,"bottom":239.5,"right":413.5,"top":254.5}},{"unicode":1206,"advance":0.59999999999999998,"planeBounds":{"left":-0.15803846153846146,"bottom":-0.35884615384615376,"right":0.76503846153846167,"top":0.94884615384615401},"atlasBounds":{"left":142.5,"bottom":306.5,"right":154.5,"top":323.5}},{"unicode":1207,"advance":0.59999999999999998,"planeBounds":{"left":-0.15503846153846143,"bottom":-0.33346153846153842,"right":0.76803846153846167,"top":0.7434615384615384},"atlasBounds":{"left":476.5,"bottom":159.5,"right":488.5,"top":173.5}},{"unicode":1210,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692295,"bottom":-0.2119230769230768,"right":0.73307692307692307,"top":0.94192307692307697},"atlasBounds":{"left":365.5,"bottom":239.5,"right":376.5,"top":254.5}},{"unicode":1211,"advance":0.59999999999999998,"planeBounds":{"left":-0.112076923076923,"bottom":-0.22499999999999995,"right":0.73407692307692307,"top":0.77500000000000002},"atlasBounds":{"left":208.5,"bottom":129.5,"right":219.5,"top":142.5}},{"unicode":1240,"advance":0.59999999999999998,"planeBounds":{"left":-0.126076923076923,"bottom":-0.2119230769230768,"right":0.72007692307692306,"top":0.94192307692307697},"atlasBounds":{"left":341.5,"bottom":239.5,"right":352.5,"top":254.5}},{"unicode":1241,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.22499999999999989,"right":0.72307692307692306,"top":0.77500000000000002},"atlasBounds":{"left":196.5,"bottom":129.5,"right":207.5,"top":142.5}},{"unicode":1256,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":317.5,"bottom":239.5,"right":328.5,"top":254.5}},{"unicode":1257,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.22499999999999989,"right":0.72307692307692306,"top":0.77500000000000002},"atlasBounds":{"left":184.5,"bottom":129.5,"right":195.5,"top":142.5}},{"unicode":2794,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.21846153846153835,"right":0.72307692307692306,"top":0.8584615384615385},"atlasBounds":{"left":449.5,"bottom":159.5,"right":460.5,"top":173.5}},{"unicode":7808,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999996,"bottom":-0.21730769230769215,"right":0.80000000000000004,"top":1.1673076923076924},"atlasBounds":{"left":200.5,"bottom":361.5,"right":213.5,"top":379.5}},{"unicode":7809,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.22288461538461524,"right":0.76153846153846172,"top":1.0078846153846157},"atlasBounds":{"left":473.5,"bottom":255.5,"right":485.5,"top":271.5}},{"unicode":7810,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999996,"bottom":-0.21730769230769215,"right":0.80000000000000004,"top":1.1673076923076924},"atlasBounds":{"left":214.5,"bottom":361.5,"right":227.5,"top":379.5}},{"unicode":7811,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.22288461538461524,"right":0.76153846153846172,"top":1.0078846153846157},"atlasBounds":{"left":494.5,"bottom":255.5,"right":506.5,"top":271.5}},{"unicode":7812,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999996,"bottom":-0.21980769230769215,"right":0.80000000000000004,"top":1.1648076923076924},"atlasBounds":{"left":228.5,"bottom":361.5,"right":241.5,"top":379.5}},{"unicode":7813,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.2268846153846153,"right":0.76153846153846172,"top":1.0038846153846155},"atlasBounds":{"left":0.5,"bottom":238.5,"right":12.5,"top":254.5}},{"unicode":7838,"advance":0.59999999999999998,"planeBounds":{"left":-0.10707692307692299,"bottom":-0.2119230769230768,"right":0.73907692307692308,"top":0.94192307692307697},"atlasBounds":{"left":206.5,"bottom":239.5,"right":217.5,"top":254.5}},{"unicode":7840,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.43380769230769217,"right":0.76153846153846172,"top":0.95080769230769246},"atlasBounds":{"left":242.5,"bottom":361.5,"right":254.5,"top":379.5}},{"unicode":7841,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.44188461538461532,"right":0.71057692307692311,"top":0.78888461538461552},"atlasBounds":{"left":13.5,"bottom":238.5,"right":24.5,"top":254.5}},{"unicode":7842,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.20230769230769227,"right":0.76153846153846172,"top":1.1823076923076925},"atlasBounds":{"left":255.5,"bottom":361.5,"right":267.5,"top":379.5}},{"unicode":7843,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.20788461538461525,"right":0.71057692307692311,"top":1.0228846153846156},"atlasBounds":{"left":34.5,"bottom":238.5,"right":45.5,"top":254.5}},{"unicode":7844,"advance":0.59999999999999998,"planeBounds":{"left":-0.16749999999999993,"bottom":-0.22076923076923061,"right":0.83250000000000002,"top":1.2407692307692311},"atlasBounds":{"left":248.5,"bottom":440.5,"right":261.5,"top":459.5}},{"unicode":7845,"advance":0.59999999999999998,"planeBounds":{"left":-0.15249999999999991,"bottom":-0.20884615384615379,"right":0.84750000000000003,"top":1.098846153846154},"atlasBounds":{"left":155.5,"bottom":306.5,"right":168.5,"top":323.5}},{"unicode":7846,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.22076923076923061,"right":0.76153846153846172,"top":1.2407692307692311},"atlasBounds":{"left":262.5,"bottom":440.5,"right":274.5,"top":459.5}},{"unicode":7847,"advance":0.59999999999999998,"planeBounds":{"left":-0.15403846153846143,"bottom":-0.20884615384615379,"right":0.76903846153846167,"top":1.098846153846154},"atlasBounds":{"left":184.5,"bottom":306.5,"right":196.5,"top":323.5}},{"unicode":7848,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.22076923076923061,"right":0.76153846153846172,"top":1.2407692307692311},"atlasBounds":{"left":275.5,"bottom":440.5,"right":287.5,"top":459.5}},{"unicode":7849,"advance":0.59999999999999998,"planeBounds":{"left":-0.15153846153846143,"bottom":-0.23230769230769227,"right":0.77153846153846173,"top":1.1523076923076925},"atlasBounds":{"left":268.5,"bottom":361.5,"right":280.5,"top":379.5}},{"unicode":7850,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.22076923076923061,"right":0.76153846153846172,"top":1.2407692307692311},"atlasBounds":{"left":288.5,"bottom":440.5,"right":300.5,"top":459.5}},{"unicode":7851,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.23330769230769222,"right":0.71057692307692311,"top":1.1513076923076924},"atlasBounds":{"left":281.5,"bottom":361.5,"right":292.5,"top":379.5}},{"unicode":7852,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.43919230769230755,"right":0.76153846153846172,"top":1.176192307692308},"atlasBounds":{"left":446.5,"bottom":464.5,"right":458.5,"top":485.5}},{"unicode":7853,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.40630769230769215,"right":0.71057692307692311,"top":0.97830769230769254},"atlasBounds":{"left":293.5,"bottom":361.5,"right":304.5,"top":379.5}},{"unicode":7854,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.22076923076923061,"right":0.76153846153846172,"top":1.2407692307692311},"atlasBounds":{"left":301.5,"bottom":440.5,"right":313.5,"top":459.5}},{"unicode":7855,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.21384615384615377,"right":0.71057692307692311,"top":1.0938461538461541},"atlasBounds":{"left":197.5,"bottom":306.5,"right":208.5,"top":323.5}},{"unicode":7856,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.22076923076923061,"right":0.76153846153846172,"top":1.2407692307692311},"atlasBounds":{"left":314.5,"bottom":440.5,"right":326.5,"top":459.5}},{"unicode":7857,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.21384615384615377,"right":0.71057692307692311,"top":1.0938461538461541},"atlasBounds":{"left":209.5,"bottom":306.5,"right":220.5,"top":323.5}},{"unicode":7858,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.22076923076923061,"right":0.76153846153846172,"top":1.2407692307692311},"atlasBounds":{"left":327.5,"bottom":440.5,"right":339.5,"top":459.5}},{"unicode":7859,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.23230769230769227,"right":0.71057692307692311,"top":1.1523076923076925},"atlasBounds":{"left":305.5,"bottom":361.5,"right":316.5,"top":379.5}},{"unicode":7860,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.22076923076923061,"right":0.76153846153846172,"top":1.2407692307692311},"atlasBounds":{"left":340.5,"bottom":440.5,"right":352.5,"top":459.5}},{"unicode":7861,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.23330769230769222,"right":0.71057692307692311,"top":1.1513076923076924},"atlasBounds":{"left":317.5,"bottom":361.5,"right":328.5,"top":379.5}},{"unicode":7862,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.43919230769230755,"right":0.76153846153846172,"top":1.176192307692308},"atlasBounds":{"left":459.5,"bottom":464.5,"right":471.5,"top":485.5}},{"unicode":7863,"advance":0.59999999999999998,"planeBounds":{"left":-0.13557692307692304,"bottom":-0.40630769230769215,"right":0.71057692307692311,"top":0.97830769230769254},"atlasBounds":{"left":329.5,"bottom":361.5,"right":340.5,"top":379.5}},{"unicode":7864,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692298,"bottom":-0.43380769230769217,"right":0.73307692307692307,"top":0.95080769230769246},"atlasBounds":{"left":341.5,"bottom":361.5,"right":352.5,"top":379.5}},{"unicode":7865,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.44188461538461532,"right":0.72307692307692306,"top":0.78888461538461552},"atlasBounds":{"left":398.5,"bottom":255.5,"right":409.5,"top":271.5}},{"unicode":7866,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692298,"bottom":-0.20230769230769227,"right":0.73307692307692307,"top":1.1823076923076925},"atlasBounds":{"left":353.5,"bottom":361.5,"right":364.5,"top":379.5}},{"unicode":7867,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.20788461538461525,"right":0.72307692307692306,"top":1.0228846153846156},"atlasBounds":{"left":374.5,"bottom":255.5,"right":385.5,"top":271.5}},{"unicode":7868,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692298,"bottom":-0.21980769230769215,"right":0.73307692307692307,"top":1.1648076923076924},"atlasBounds":{"left":365.5,"bottom":361.5,"right":376.5,"top":379.5}},{"unicode":7869,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.2303846153846153,"right":0.72307692307692306,"top":1.0003846153846154},"atlasBounds":{"left":350.5,"bottom":255.5,"right":361.5,"top":271.5}},{"unicode":7870,"advance":0.59999999999999998,"planeBounds":{"left":-0.094038461538461432,"bottom":-0.22076923076923061,"right":0.82903846153846172,"top":1.2407692307692311},"atlasBounds":{"left":353.5,"bottom":440.5,"right":365.5,"top":459.5}},{"unicode":7871,"advance":0.59999999999999998,"planeBounds":{"left":-0.14499999999999988,"bottom":-0.20884615384615379,"right":0.85499999999999998,"top":1.098846153846154},"atlasBounds":{"left":221.5,"bottom":306.5,"right":234.5,"top":323.5}},{"unicode":7872,"advance":0.59999999999999998,"planeBounds":{"left":-0.093076923076923015,"bottom":-0.22076923076923061,"right":0.75307692307692309,"top":1.2407692307692311},"atlasBounds":{"left":366.5,"bottom":440.5,"right":377.5,"top":459.5}},{"unicode":7873,"advance":0.59999999999999998,"planeBounds":{"left":-0.10807692307692301,"bottom":-0.20884615384615379,"right":0.73807692307692307,"top":1.098846153846154},"atlasBounds":{"left":235.5,"bottom":306.5,"right":246.5,"top":323.5}},{"unicode":7874,"advance":0.59999999999999998,"planeBounds":{"left":-0.12653846153846143,"bottom":-0.22076923076923061,"right":0.79653846153846164,"top":1.2407692307692311},"atlasBounds":{"left":378.5,"bottom":440.5,"right":390.5,"top":459.5}},{"unicode":7875,"advance":0.59999999999999998,"planeBounds":{"left":-0.14403846153846145,"bottom":-0.23230769230769227,"right":0.77903846153846168,"top":1.1523076923076925},"atlasBounds":{"left":377.5,"bottom":361.5,"right":389.5,"top":379.5}},{"unicode":7876,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692298,"bottom":-0.22076923076923061,"right":0.73307692307692307,"top":1.2407692307692311},"atlasBounds":{"left":436.5,"bottom":440.5,"right":447.5,"top":459.5}},{"unicode":7877,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.23330769230769222,"right":0.72307692307692306,"top":1.1513076923076924},"atlasBounds":{"left":390.5,"bottom":361.5,"right":401.5,"top":379.5}},{"unicode":7878,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692298,"bottom":-0.43919230769230755,"right":0.73307692307692307,"top":1.176192307692308},"atlasBounds":{"left":472.5,"bottom":464.5,"right":483.5,"top":485.5}},{"unicode":7879,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.40630769230769215,"right":0.72307692307692306,"top":0.97830769230769254},"atlasBounds":{"left":402.5,"bottom":361.5,"right":413.5,"top":379.5}},{"unicode":7880,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.20230769230769227,"right":0.72307692307692306,"top":1.1823076923076925},"atlasBounds":{"left":414.5,"bottom":361.5,"right":425.5,"top":379.5}},{"unicode":7881,"advance":0.59999999999999998,"planeBounds":{"left":-0.14153846153846145,"bottom":-0.20288461538461527,"right":0.78153846153846163,"top":1.0278846153846155},"atlasBounds":{"left":206.5,"bottom":255.5,"right":218.5,"top":271.5}},{"unicode":7882,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.43380769230769217,"right":0.72307692307692306,"top":0.95080769230769246},"atlasBounds":{"left":426.5,"bottom":361.5,"right":437.5,"top":379.5}},{"unicode":7883,"advance":0.59999999999999998,"planeBounds":{"left":-0.14153846153846145,"bottom":-0.41030769230769215,"right":0.78153846153846163,"top":0.97430769230769254},"atlasBounds":{"left":438.5,"bottom":361.5,"right":450.5,"top":379.5}},{"unicode":7884,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.42880769230769222,"right":0.72307692307692306,"top":0.95580769230769236},"atlasBounds":{"left":451.5,"bottom":361.5,"right":462.5,"top":379.5}},{"unicode":7885,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.44288461538461527,"right":0.72307692307692306,"top":0.78788461538461563},"atlasBounds":{"left":157.5,"bottom":255.5,"right":168.5,"top":271.5}},{"unicode":7886,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.20730769230769217,"right":0.72307692307692306,"top":1.1773076923076924},"atlasBounds":{"left":463.5,"bottom":361.5,"right":474.5,"top":379.5}},{"unicode":7887,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.20688461538461531,"right":0.72307692307692306,"top":1.0238846153846155},"atlasBounds":{"left":132.5,"bottom":255.5,"right":143.5,"top":271.5}},{"unicode":7888,"advance":0.59999999999999998,"planeBounds":{"left":-0.11003846153846146,"bottom":-0.22576923076923061,"right":0.81303846153846171,"top":1.235769230769231},"atlasBounds":{"left":448.5,"bottom":440.5,"right":460.5,"top":459.5}},{"unicode":7889,"advance":0.59999999999999998,"planeBounds":{"left":-0.14499999999999988,"bottom":-0.20784615384615379,"right":0.85499999999999998,"top":1.0998461538461541},"atlasBounds":{"left":247.5,"bottom":306.5,"right":260.5,"top":323.5}},{"unicode":7890,"advance":0.59999999999999998,"planeBounds":{"left":-0.10907692307692299,"bottom":-0.22576923076923061,"right":0.73707692307692307,"top":1.235769230769231},"atlasBounds":{"left":461.5,"bottom":440.5,"right":472.5,"top":459.5}},{"unicode":7891,"advance":0.59999999999999998,"planeBounds":{"left":-0.10807692307692301,"bottom":-0.20784615384615379,"right":0.73807692307692307,"top":1.0998461538461541},"atlasBounds":{"left":261.5,"bottom":306.5,"right":272.5,"top":323.5}},{"unicode":7892,"advance":0.59999999999999998,"planeBounds":{"left":-0.1425384615384615,"bottom":-0.22576923076923061,"right":0.78053846153846163,"top":1.235769230769231},"atlasBounds":{"left":473.5,"bottom":440.5,"right":485.5,"top":459.5}},{"unicode":7893,"advance":0.59999999999999998,"planeBounds":{"left":-0.14403846153846145,"bottom":-0.23130769230769221,"right":0.77903846153846168,"top":1.1533076923076924},"atlasBounds":{"left":475.5,"bottom":361.5,"right":487.5,"top":379.5}},{"unicode":7894,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.22576923076923061,"right":0.72307692307692306,"top":1.235769230769231},"atlasBounds":{"left":486.5,"bottom":440.5,"right":497.5,"top":459.5}},{"unicode":7895,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.23230769230769222,"right":0.72307692307692306,"top":1.1523076923076925},"atlasBounds":{"left":488.5,"bottom":361.5,"right":499.5,"top":379.5}},{"unicode":7896,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.43919230769230755,"right":0.72307692307692306,"top":1.176192307692308},"atlasBounds":{"left":484.5,"bottom":464.5,"right":495.5,"top":485.5}},{"unicode":7897,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.40630769230769215,"right":0.72307692307692306,"top":0.97830769230769254},"atlasBounds":{"left":500.5,"bottom":361.5,"right":511.5,"top":379.5}},{"unicode":7898,"advance":0.59999999999999998,"planeBounds":{"left":-0.11407692307692301,"bottom":-0.22230769230769215,"right":0.73207692307692307,"top":1.1623076923076925},"atlasBounds":{"left":0.5,"bottom":342.5,"right":11.5,"top":360.5}},{"unicode":7899,"advance":0.59999999999999998,"planeBounds":{"left":-0.11057692307692299,"bottom":-0.2268846153846153,"right":0.73557692307692313,"top":1.0038846153846155},"atlasBounds":{"left":0.5,"bottom":255.5,"right":11.5,"top":271.5}},{"unicode":7900,"advance":0.59999999999999998,"planeBounds":{"left":-0.11407692307692301,"bottom":-0.22230769230769215,"right":0.73207692307692307,"top":1.1623076923076925},"atlasBounds":{"left":12.5,"bottom":342.5,"right":23.5,"top":360.5}},{"unicode":7901,"advance":0.59999999999999998,"planeBounds":{"left":-0.11057692307692299,"bottom":-0.2268846153846153,"right":0.73557692307692313,"top":1.0038846153846155},"atlasBounds":{"left":478.5,"bottom":272.5,"right":489.5,"top":288.5}},{"unicode":7902,"advance":0.59999999999999998,"planeBounds":{"left":-0.11407692307692301,"bottom":-0.20730769230769217,"right":0.73207692307692307,"top":1.1773076923076924},"atlasBounds":{"left":24.5,"bottom":342.5,"right":35.5,"top":360.5}},{"unicode":7903,"advance":0.59999999999999998,"planeBounds":{"left":-0.11057692307692299,"bottom":-0.20688461538461531,"right":0.73557692307692313,"top":1.0238846153846155},"atlasBounds":{"left":454.5,"bottom":272.5,"right":465.5,"top":288.5}},{"unicode":7904,"advance":0.59999999999999998,"planeBounds":{"left":-0.11407692307692301,"bottom":-0.22480769230769221,"right":0.73207692307692307,"top":1.1598076923076923},"atlasBounds":{"left":76.5,"bottom":342.5,"right":87.5,"top":360.5}},{"unicode":7905,"advance":0.59999999999999998,"planeBounds":{"left":-0.11057692307692299,"bottom":-0.22938461538461524,"right":0.73557692307692313,"top":1.0013846153846155},"atlasBounds":{"left":430.5,"bottom":272.5,"right":441.5,"top":288.5}},{"unicode":7906,"advance":0.59999999999999998,"planeBounds":{"left":-0.11407692307692301,"bottom":-0.43226923076923068,"right":0.73207692307692307,"top":1.029269230769231},"atlasBounds":{"left":0.5,"bottom":418.5,"right":11.5,"top":437.5}},{"unicode":7907,"advance":0.59999999999999998,"planeBounds":{"left":-0.11057692307692299,"bottom":-0.44034615384615383,"right":0.73557692307692313,"top":0.86734615384615399},"atlasBounds":{"left":288.5,"bottom":306.5,"right":299.5,"top":323.5}},{"unicode":7908,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.43380769230769217,"right":0.72307692307692306,"top":0.95080769230769246},"atlasBounds":{"left":88.5,"bottom":342.5,"right":99.5,"top":360.5}},{"unicode":7909,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.40842307692307683,"right":0.72307692307692306,"top":0.74542307692307697},"atlasBounds":{"left":248.5,"bottom":158.5,"right":259.5,"top":173.5}},{"unicode":7910,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.20730769230769217,"right":0.72307692307692306,"top":1.1773076923076924},"atlasBounds":{"left":100.5,"bottom":342.5,"right":111.5,"top":360.5}},{"unicode":7911,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.20788461538461525,"right":0.72307692307692306,"top":1.0228846153846156},"atlasBounds":{"left":357.5,"bottom":272.5,"right":368.5,"top":288.5}},{"unicode":7912,"advance":0.59999999999999998,"planeBounds":{"left":-0.11903846153846144,"bottom":-0.22230769230769215,"right":0.8040384615384617,"top":1.1623076923076925},"atlasBounds":{"left":112.5,"bottom":342.5,"right":124.5,"top":360.5}},{"unicode":7913,"advance":0.59999999999999998,"planeBounds":{"left":-0.11653846153846147,"bottom":-0.2278846153846153,"right":0.80653846153846165,"top":1.0028846153846156},"atlasBounds":{"left":332.5,"bottom":272.5,"right":344.5,"top":288.5}},{"unicode":7914,"advance":0.59999999999999998,"planeBounds":{"left":-0.11903846153846144,"bottom":-0.22230769230769215,"right":0.8040384615384617,"top":1.1623076923076925},"atlasBounds":{"left":125.5,"bottom":342.5,"right":137.5,"top":360.5}},{"unicode":7915,"advance":0.59999999999999998,"planeBounds":{"left":-0.11653846153846147,"bottom":-0.2278846153846153,"right":0.80653846153846165,"top":1.0028846153846156},"atlasBounds":{"left":306.5,"bottom":272.5,"right":318.5,"top":288.5}},{"unicode":7916,"advance":0.59999999999999998,"planeBounds":{"left":-0.11903846153846144,"bottom":-0.20730769230769217,"right":0.8040384615384617,"top":1.1773076923076924},"atlasBounds":{"left":138.5,"bottom":342.5,"right":150.5,"top":360.5}},{"unicode":7917,"advance":0.59999999999999998,"planeBounds":{"left":-0.11653846153846147,"bottom":-0.20788461538461525,"right":0.80653846153846165,"top":1.0228846153846156},"atlasBounds":{"left":281.5,"bottom":272.5,"right":293.5,"top":288.5}},{"unicode":7918,"advance":0.59999999999999998,"planeBounds":{"left":-0.11903846153846144,"bottom":-0.22480769230769221,"right":0.8040384615384617,"top":1.1598076923076923},"atlasBounds":{"left":151.5,"bottom":342.5,"right":163.5,"top":360.5}},{"unicode":7919,"advance":0.59999999999999998,"planeBounds":{"left":-0.11653846153846147,"bottom":-0.2303846153846153,"right":0.80653846153846165,"top":1.0003846153846154},"atlasBounds":{"left":256.5,"bottom":272.5,"right":268.5,"top":288.5}},{"unicode":7920,"advance":0.59999999999999998,"planeBounds":{"left":-0.11903846153846144,"bottom":-0.41726923076923067,"right":0.8040384615384617,"top":1.0442692307692309},"atlasBounds":{"left":12.5,"bottom":418.5,"right":24.5,"top":437.5}},{"unicode":7921,"advance":0.59999999999999998,"planeBounds":{"left":-0.11653846153846147,"bottom":-0.43034615384615371,"right":0.80653846153846165,"top":0.87734615384615411},"atlasBounds":{"left":300.5,"bottom":306.5,"right":312.5,"top":323.5}},{"unicode":7922,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.21730769230769215,"right":0.76153846153846172,"top":1.1673076923076924},"atlasBounds":{"left":164.5,"bottom":342.5,"right":176.5,"top":360.5}},{"unicode":7923,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.3898076923076923,"right":0.76153846153846172,"top":0.99480769230769239},"atlasBounds":{"left":177.5,"bottom":342.5,"right":189.5,"top":360.5}},{"unicode":7924,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.43380769230769217,"right":0.76153846153846172,"top":0.95080769230769246},"atlasBounds":{"left":190.5,"bottom":342.5,"right":202.5,"top":360.5}},{"unicode":7925,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.40842307692307683,"right":0.76153846153846172,"top":0.74542307692307697},"atlasBounds":{"left":148.5,"bottom":158.5,"right":160.5,"top":173.5}},{"unicode":7926,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.20230769230769227,"right":0.76153846153846172,"top":1.1823076923076925},"atlasBounds":{"left":203.5,"bottom":342.5,"right":215.5,"top":360.5}},{"unicode":7927,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.40826923076923061,"right":0.76153846153846172,"top":1.0532692307692311},"atlasBounds":{"left":25.5,"bottom":418.5,"right":37.5,"top":437.5}},{"unicode":7928,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.21980769230769215,"right":0.76153846153846172,"top":1.1648076923076924},"atlasBounds":{"left":216.5,"bottom":342.5,"right":228.5,"top":360.5}},{"unicode":7929,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.39230769230769214,"right":0.76153846153846172,"top":0.99230769230769256},"atlasBounds":{"left":229.5,"bottom":342.5,"right":241.5,"top":360.5}},{"unicode":8193,"advance":0.59999999999999998},{"unicode":8203,"advance":0.59999999999999998},{"unicode":8208,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384565,"bottom":0.060769230769230839,"right":0.68461538461538463,"top":0.59923076923076934},"atlasBounds":{"left":373.5,"bottom":57.5,"right":383.5,"top":64.5}},{"unicode":8211,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":0.060769230769230839,"right":0.72307692307692306,"top":0.59923076923076934},"atlasBounds":{"left":438.5,"bottom":57.5,"right":449.5,"top":64.5}},{"unicode":8212,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":0.060769230769230839,"right":0.80000000000000004,"top":0.59923076923076934},"atlasBounds":{"left":450.5,"bottom":57.5,"right":463.5,"top":64.5}},{"unicode":8216,"advance":0.59999999999999998,"planeBounds":{"left":0.027307692307692387,"bottom":0.23488461538461547,"right":0.64269230769230778,"top":1.0041153846153847},"atlasBounds":{"left":321.5,"bottom":76.5,"right":329.5,"top":86.5}},{"unicode":8217,"advance":0.59999999999999998,"planeBounds":{"left":-0.0076923076923076155,"bottom":0.23588461538461547,"right":0.60769230769230775,"top":1.0051153846153846},"atlasBounds":{"left":330.5,"bottom":76.5,"right":338.5,"top":86.5}},{"unicode":8218,"advance":0.59999999999999998,"planeBounds":{"left":-0.037692307692307615,"bottom":-0.38911538461538459,"right":0.57769230769230773,"top":0.38011538461538458},"atlasBounds":{"left":339.5,"bottom":76.5,"right":347.5,"top":86.5}},{"unicode":8220,"advance":0.59999999999999998,"planeBounds":{"left":-0.128076923076923,"bottom":0.23588461538461547,"right":0.71807692307692306,"top":1.0051153846153846},"atlasBounds":{"left":291.5,"bottom":76.5,"right":302.5,"top":86.5}},{"unicode":8221,"advance":0.59999999999999998,"planeBounds":{"left":-0.11807692307692301,"bottom":0.23588461538461547,"right":0.72807692307692307,"top":1.0051153846153846},"atlasBounds":{"left":279.5,"bottom":76.5,"right":290.5,"top":86.5}},{"unicode":8222,"advance":0.59999999999999998,"planeBounds":{"left":-0.11807692307692301,"bottom":-0.38911538461538459,"right":0.72807692307692307,"top":0.38011538461538458},"atlasBounds":{"left":184.5,"bottom":76.5,"right":195.5,"top":86.5}},{"unicode":8224,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.33230769230769225,"right":0.72307692307692306,"top":1.0523076923076924},"atlasBounds":{"left":242.5,"bottom":342.5,"right":253.5,"top":360.5}},{"unicode":8225,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.33230769230769225,"right":0.72307692307692306,"top":1.0523076923076924},"atlasBounds":{"left":254.5,"bottom":342.5,"right":265.5,"top":360.5}},{"unicode":8226,"advance":0.59999999999999998,"planeBounds":{"left":-0.046153846153846052,"bottom":0.018846153846153946,"right":0.64615384615384619,"top":0.71115384615384625},"atlasBounds":{"left":407.5,"bottom":77.5,"right":416.5,"top":86.5}},{"unicode":8230,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":-0.22769230769230769,"right":0.76153846153846172,"top":0.38769230769230767},"atlasBounds":{"left":302.5,"bottom":56.5,"right":314.5,"top":64.5}},{"unicode":8240,"advance":0.59999999999999998,"planeBounds":{"left":-0.2039999999999999,"bottom":-0.21192307692307685,"right":0.79600000000000004,"top":0.94192307692307697},"atlasBounds":{"left":498.5,"bottom":190.5,"right":511.5,"top":205.5}},{"unicode":8242,"advance":0.59999999999999998,"planeBounds":{"left":-0.037692307692307615,"bottom":0.23588461538461547,"right":0.57769230769230773,"top":1.0051153846153846},"atlasBounds":{"left":107.5,"bottom":76.5,"right":115.5,"top":86.5}},{"unicode":8243,"advance":0.59999999999999998,"planeBounds":{"left":-0.11807692307692301,"bottom":0.23588461538461547,"right":0.72807692307692307,"top":1.0051153846153846},"atlasBounds":{"left":85.5,"bottom":76.5,"right":96.5,"top":86.5}},{"unicode":8244,"advance":0.59999999999999998,"planeBounds":{"left":-0.1999999999999999,"bottom":0.23588461538461547,"right":0.80000000000000004,"top":1.0051153846153846},"atlasBounds":{"left":196.5,"bottom":76.5,"right":209.5,"top":86.5}},{"unicode":8249,"advance":0.59999999999999998,"planeBounds":{"left":-0.045653846153846038,"bottom":-0.18153846153846148,"right":0.64665384615384625,"top":0.7415384615384617},"atlasBounds":{"left":0.5,"bottom":87.5,"right":9.5,"top":99.5}},{"unicode":8250,"advance":0.59999999999999998,"planeBounds":{"left":-0.045653846153846038,"bottom":-0.18153846153846148,"right":0.64665384615384625,"top":0.7415384615384617},"atlasBounds":{"left":23.5,"bottom":87.5,"right":32.5,"top":99.5}},{"unicode":8254,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":0.52076923076923087,"right":0.83846153846153837,"top":1.0592307692307692},"atlasBounds":{"left":358.5,"bottom":57.5,"right":372.5,"top":64.5}},{"unicode":8255,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.40269230769230768,"right":0.83846153846153848,"top":0.21269230769230768},"atlasBounds":{"left":163.5,"bottom":56.5,"right":177.5,"top":64.5}},{"unicode":8260,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":398.5,"bottom":190.5,"right":409.5,"top":205.5}},{"unicode":8261,"advance":0.59999999999999998,"planeBounds":{"left":-0.01865384615384607,"bottom":-0.33230769230769225,"right":0.67365384615384616,"top":1.0523076923076924},"atlasBounds":{"left":266.5,"bottom":342.5,"right":275.5,"top":360.5}},{"unicode":8262,"advance":0.59999999999999998,"planeBounds":{"left":-0.07365384615384607,"bottom":-0.33230769230769225,"right":0.61865384615384622,"top":1.0523076923076924},"atlasBounds":{"left":276.5,"bottom":342.5,"right":285.5,"top":360.5}},{"unicode":8304,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384495,"bottom":0.19442307692307698,"right":0.68461538461538463,"top":1.0405769230769231},"atlasBounds":{"left":451.5,"bottom":88.5,"right":461.5,"top":99.5}},{"unicode":8308,"advance":0.59999999999999998,"planeBounds":{"left":-0.059153846153846085,"bottom":0.194923076923077,"right":0.63315384615384618,"top":1.041076923076923},"atlasBounds":{"left":0.5,"bottom":75.5,"right":9.5,"top":86.5}},{"unicode":8309,"advance":0.59999999999999998,"planeBounds":{"left":-0.060153846153846072,"bottom":0.18942307692307703,"right":0.63215384615384618,"top":1.0355769230769232},"atlasBounds":{"left":26.5,"bottom":75.5,"right":35.5,"top":86.5}},{"unicode":8310,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384495,"bottom":0.18942307692307703,"right":0.68461538461538463,"top":1.0355769230769232},"atlasBounds":{"left":222.5,"bottom":88.5,"right":232.5,"top":99.5}},{"unicode":8311,"advance":0.59999999999999998,"planeBounds":{"left":-0.046153846153846038,"bottom":0.194423076923077,"right":0.64615384615384619,"top":1.0405769230769231},"atlasBounds":{"left":233.5,"bottom":88.5,"right":242.5,"top":99.5}},{"unicode":8312,"advance":0.59999999999999998,"planeBounds":{"left":-0.04615384615384608,"bottom":0.19442307692307698,"right":0.64615384615384619,"top":1.0405769230769231},"atlasBounds":{"left":271.5,"bottom":88.5,"right":280.5,"top":99.5}},{"unicode":8313,"advance":0.59999999999999998,"planeBounds":{"left":-0.083615384615384508,"bottom":0.19942307692307704,"right":0.68561538461538463,"top":1.0455769230769232},"atlasBounds":{"left":281.5,"bottom":88.5,"right":291.5,"top":99.5}},{"unicode":8314,"advance":0.59999999999999998,"planeBounds":{"left":-0.046153846153846066,"bottom":0.098846153846153931,"right":0.64615384615384619,"top":0.79115384615384621},"atlasBounds":{"left":502.5,"bottom":65.5,"right":511.5,"top":74.5}},{"unicode":8320,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384495,"bottom":-0.32057692307692304,"right":0.68461538461538463,"top":0.52557692307692305},"atlasBounds":{"left":306.5,"bottom":88.5,"right":316.5,"top":99.5}},{"unicode":8321,"advance":0.59999999999999998,"planeBounds":{"left":-0.038653846153846039,"bottom":-0.32057692307692298,"right":0.65365384615384625,"top":0.52557692307692305},"atlasBounds":{"left":317.5,"bottom":88.5,"right":326.5,"top":99.5}},{"unicode":8322,"advance":0.59999999999999998,"planeBounds":{"left":-0.043153846153846098,"bottom":-0.31557692307692298,"right":0.64915384615384619,"top":0.53057692307692306},"atlasBounds":{"left":327.5,"bottom":88.5,"right":336.5,"top":99.5}},{"unicode":8323,"advance":0.59999999999999998,"planeBounds":{"left":-0.062153846153846067,"bottom":-0.32507692307692304,"right":0.63015384615384618,"top":0.5210769230769231},"atlasBounds":{"left":349.5,"bottom":88.5,"right":358.5,"top":99.5}},{"unicode":8324,"advance":0.59999999999999998,"planeBounds":{"left":-0.059153846153846085,"bottom":-0.32007692307692304,"right":0.63315384615384618,"top":0.52607692307692311},"atlasBounds":{"left":359.5,"bottom":88.5,"right":368.5,"top":99.5}},{"unicode":8325,"advance":0.59999999999999998,"planeBounds":{"left":-0.060153846153846072,"bottom":-0.32557692307692304,"right":0.63215384615384618,"top":0.52057692307692316},"atlasBounds":{"left":369.5,"bottom":88.5,"right":378.5,"top":99.5}},{"unicode":8326,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384495,"bottom":-0.32557692307692304,"right":0.68461538461538463,"top":0.52057692307692316},"atlasBounds":{"left":379.5,"bottom":88.5,"right":389.5,"top":99.5}},{"unicode":8327,"advance":0.59999999999999998,"planeBounds":{"left":-0.046153846153846038,"bottom":-0.32057692307692298,"right":0.64615384615384619,"top":0.52557692307692305},"atlasBounds":{"left":390.5,"bottom":88.5,"right":399.5,"top":99.5}},{"unicode":8328,"advance":0.59999999999999998,"planeBounds":{"left":-0.04615384615384608,"bottom":-0.33057692307692305,"right":0.64615384615384619,"top":0.51557692307692315},"atlasBounds":{"left":400.5,"bottom":88.5,"right":409.5,"top":99.5}},{"unicode":8329,"advance":0.59999999999999998,"planeBounds":{"left":-0.083615384615384508,"bottom":-0.31557692307692298,"right":0.68561538461538463,"top":0.53057692307692306},"atlasBounds":{"left":410.5,"bottom":88.5,"right":420.5,"top":99.5}},{"unicode":8363,"advance":0.59999999999999998,"planeBounds":{"left":-0.11653846153846147,"bottom":-0.2903846153846153,"right":0.80653846153846165,"top":0.94038461538461549},"atlasBounds":{"left":108.5,"bottom":289.5,"right":120.5,"top":305.5}},{"unicode":8364,"advance":0.59999999999999998,"planeBounds":{"left":-0.18203846153846143,"bottom":-0.21692307692307686,"right":0.74103846153846165,"top":0.93692307692307697},"atlasBounds":{"left":274.5,"bottom":190.5,"right":286.5,"top":205.5}},{"unicode":8366,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":249.5,"bottom":190.5,"right":261.5,"top":205.5}},{"unicode":8381,"advance":0.59999999999999998,"planeBounds":{"left":-0.17153846153846145,"bottom":-0.2119230769230768,"right":0.75153846153846171,"top":0.94192307692307697},"atlasBounds":{"left":236.5,"bottom":190.5,"right":248.5,"top":205.5}},{"unicode":8383,"advance":0.59999999999999998,"planeBounds":{"left":-0.11157692307692298,"bottom":-0.36526923076923062,"right":0.73457692307692313,"top":1.096269230769231},"atlasBounds":{"left":38.5,"bottom":418.5,"right":49.5,"top":437.5}},{"unicode":8467,"advance":0.59999999999999998,"planeBounds":{"left":-0.13807692307692296,"bottom":-0.20692307692307685,"right":0.70807692307692316,"top":0.94692307692307698},"atlasBounds":{"left":224.5,"bottom":190.5,"right":235.5,"top":205.5}},{"unicode":8469,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":212.5,"bottom":190.5,"right":223.5,"top":205.5}},{"unicode":8470,"advance":0.59999999999999998,"planeBounds":{"left":-0.18999999999999989,"bottom":-0.20692307692307685,"right":0.81000000000000005,"top":0.94692307692307698},"atlasBounds":{"left":198.5,"bottom":190.5,"right":211.5,"top":205.5}},{"unicode":8474,"advance":0.59999999999999998,"planeBounds":{"left":-0.11557692307692301,"bottom":-0.37384615384615372,"right":0.73057692307692312,"top":0.9338461538461541},"atlasBounds":{"left":343.5,"bottom":306.5,"right":354.5,"top":323.5}},{"unicode":8482,"advance":0.59999999999999998,"planeBounds":{"left":-0.20999999999999994,"bottom":0.16538461538461549,"right":0.79000000000000004,"top":0.93461538461538463},"atlasBounds":{"left":116.5,"bottom":76.5,"right":129.5,"top":86.5}},{"unicode":8484,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":125.5,"bottom":190.5,"right":136.5,"top":205.5}},{"unicode":8494,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":50.5,"bottom":206.5,"right":62.5,"top":221.5}},{"unicode":8512,"advance":0.59999999999999998,"planeBounds":{"left":-0.16403846153846144,"bottom":-0.37884615384615383,"right":0.75903846153846166,"top":0.92884615384615399},"atlasBounds":{"left":355.5,"bottom":306.5,"right":367.5,"top":323.5}},{"unicode":8586,"advance":0.59999999999999998,"planeBounds":{"left":-0.12207692307692299,"bottom":-0.21692307692307686,"right":0.72407692307692306,"top":0.93692307692307697},"atlasBounds":{"left":38.5,"bottom":206.5,"right":49.5,"top":221.5}},{"unicode":8587,"advance":0.59999999999999998,"planeBounds":{"left":-0.11307692307692301,"bottom":-0.20692307692307685,"right":0.73307692307692307,"top":0.94692307692307698},"atlasBounds":{"left":477.5,"bottom":222.5,"right":488.5,"top":237.5}},{"unicode":8592,"advance":0.59999999999999998,"planeBounds":{"left":-0.15653846153846146,"bottom":-0.1699999999999999,"right":0.76653846153846172,"top":0.83000000000000007},"atlasBounds":{"left":319.5,"bottom":114.5,"right":331.5,"top":127.5}},{"unicode":8593,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.20692307692307685,"right":0.80000000000000004,"top":0.94692307692307698},"atlasBounds":{"left":426.5,"bottom":222.5,"right":439.5,"top":237.5}},{"unicode":8594,"advance":0.59999999999999998,"planeBounds":{"left":-0.15653846153846146,"bottom":-0.1699999999999999,"right":0.76653846153846172,"top":0.83000000000000007},"atlasBounds":{"left":282.5,"bottom":114.5,"right":294.5,"top":127.5}},{"unicode":8595,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.21692307692307686,"right":0.80000000000000004,"top":0.93692307692307697},"atlasBounds":{"left":299.5,"bottom":222.5,"right":312.5,"top":237.5}},{"unicode":8596,"advance":0.59999999999999998,"planeBounds":{"left":-0.27692307692307683,"bottom":-0.1699999999999999,"right":0.87692307692307692,"top":0.83000000000000007},"atlasBounds":{"left":496.5,"bottom":472.5,"right":511.5,"top":485.5}},{"unicode":8597,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.36576923076923068,"right":0.80000000000000004,"top":1.0957692307692311},"atlasBounds":{"left":498.5,"bottom":440.5,"right":511.5,"top":459.5}},{"unicode":8598,"advance":0.59999999999999998,"planeBounds":{"left":-0.18249999999999991,"bottom":-0.15249999999999991,"right":0.8175,"top":0.84750000000000003},"atlasBounds":{"left":171.5,"bottom":100.5,"right":184.5,"top":113.5}},{"unicode":8599,"advance":0.59999999999999998,"planeBounds":{"left":-0.21749999999999992,"bottom":-0.15199999999999991,"right":0.78249999999999997,"top":0.84799999999999998},"atlasBounds":{"left":198.5,"bottom":100.5,"right":211.5,"top":113.5}},{"unicode":8600,"advance":0.59999999999999998,"planeBounds":{"left":-0.21749999999999992,"bottom":-0.15249999999999991,"right":0.78249999999999997,"top":0.84750000000000003},"atlasBounds":{"left":203.5,"bottom":114.5,"right":216.5,"top":127.5}},{"unicode":8601,"advance":0.59999999999999998,"planeBounds":{"left":-0.18249999999999991,"bottom":-0.15349999999999989,"right":0.8175,"top":0.84650000000000003},"atlasBounds":{"left":37.5,"bottom":114.5,"right":50.5,"top":127.5}},{"unicode":8606,"advance":0.59999999999999998,"planeBounds":{"left":-0.27692307692307683,"bottom":-0.14807692307692299,"right":0.87692307692307692,"top":0.69807692307692315},"atlasBounds":{"left":243.5,"bottom":88.5,"right":258.5,"top":99.5}},{"unicode":8608,"advance":0.59999999999999998,"planeBounds":{"left":-0.27692307692307683,"bottom":-0.14807692307692299,"right":0.87692307692307692,"top":0.69807692307692315},"atlasBounds":{"left":206.5,"bottom":88.5,"right":221.5,"top":99.5}},{"unicode":8611,"advance":0.59999999999999998,"planeBounds":{"left":-0.27692307692307683,"bottom":-0.14807692307692299,"right":0.87692307692307692,"top":0.69807692307692315},"atlasBounds":{"left":492.5,"bottom":88.5,"right":507.5,"top":99.5}},{"unicode":8613,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.21499999999999989,"right":0.72307692307692306,"top":0.78500000000000003},"atlasBounds":{"left":421.5,"bottom":114.5,"right":432.5,"top":127.5}},{"unicode":8614,"advance":0.59999999999999998,"planeBounds":{"left":-0.27692307692307683,"bottom":-0.14807692307692299,"right":0.87692307692307692,"top":0.69807692307692315},"atlasBounds":{"left":10.5,"bottom":75.5,"right":25.5,"top":86.5}},{"unicode":8615,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2349999999999999,"right":0.72307692307692306,"top":0.76500000000000001},"atlasBounds":{"left":105.5,"bottom":100.5,"right":116.5,"top":113.5}},{"unicode":8621,"advance":0.59999999999999998,"planeBounds":{"left":-0.27692307692307683,"bottom":-0.14807692307692299,"right":0.87692307692307692,"top":0.69807692307692315},"atlasBounds":{"left":496.5,"bottom":116.5,"right":511.5,"top":127.5}},{"unicode":8649,"advance":0.59999999999999998,"planeBounds":{"left":-0.27692307692307683,"bottom":-0.26096153846153836,"right":0.87692307692307692,"top":0.81596153846153852},"atlasBounds":{"left":410.5,"bottom":159.5,"right":425.5,"top":173.5}},{"unicode":8657,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.40126923076923066,"right":0.83846153846153837,"top":1.060269230769231},"atlasBounds":{"left":50.5,"bottom":418.5,"right":64.5,"top":437.5}},{"unicode":8658,"advance":0.59999999999999998,"planeBounds":{"left":-0.27692307692307683,"bottom":-0.1699999999999999,"right":0.87692307692307692,"top":0.83000000000000007},"atlasBounds":{"left":115.5,"bottom":129.5,"right":130.5,"top":142.5}},{"unicode":8659,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.41126923076923066,"right":0.83846153846153837,"top":1.050269230769231},"atlasBounds":{"left":65.5,"bottom":418.5,"right":79.5,"top":437.5}},{"unicode":8660,"advance":0.59999999999999998,"planeBounds":{"left":-0.27692307692307683,"bottom":-0.1699999999999999,"right":0.87692307692307692,"top":0.83000000000000007},"atlasBounds":{"left":220.5,"bottom":129.5,"right":235.5,"top":142.5}},{"unicode":8679,"advance":0.59999999999999998,"planeBounds":{"left":-0.1999999999999999,"bottom":-0.19942307692307681,"right":0.80000000000000004,"top":0.95442307692307693},"atlasBounds":{"left":465.5,"bottom":239.5,"right":478.5,"top":254.5}},{"unicode":8680,"advance":0.59999999999999998,"planeBounds":{"left":-0.22346153846153841,"bottom":-0.22499999999999989,"right":0.85346153846153838,"top":0.77500000000000002},"atlasBounds":{"left":236.5,"bottom":129.5,"right":250.5,"top":142.5}},{"unicode":8704,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":187.5,"bottom":222.5,"right":199.5,"top":237.5}},{"unicode":8705,"advance":0.59999999999999998,"planeBounds":{"left":-0.041153846153846083,"bottom":-0.22499999999999989,"right":0.65115384615384619,"top":0.77500000000000002},"atlasBounds":{"left":79.5,"bottom":114.5,"right":88.5,"top":127.5}},{"unicode":8706,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.21692307692307686,"right":0.72307692307692306,"top":0.93692307692307697},"atlasBounds":{"left":180.5,"bottom":206.5,"right":191.5,"top":221.5}},{"unicode":8707,"advance":0.59999999999999998,"planeBounds":{"left":-0.13307692307692298,"bottom":-0.2119230769230768,"right":0.71307692307692316,"top":0.94192307692307697},"atlasBounds":{"left":217.5,"bottom":206.5,"right":228.5,"top":221.5}},{"unicode":8709,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.2119230769230768,"right":0.83846153846153837,"top":0.94192307692307697},"atlasBounds":{"left":440.5,"bottom":206.5,"right":454.5,"top":221.5}},{"unicode":8712,"advance":0.59999999999999998,"planeBounds":{"left":-0.15903846153846143,"bottom":-0.13999999999999993,"right":0.76403846153846167,"top":0.85999999999999999},"atlasBounds":{"left":52.5,"bottom":100.5,"right":64.5,"top":113.5}},{"unicode":8713,"advance":0.59999999999999998,"planeBounds":{"left":-0.15903846153846143,"bottom":-0.25538461538461527,"right":0.76403846153846167,"top":0.97538461538461563},"atlasBounds":{"left":316.5,"bottom":255.5,"right":328.5,"top":271.5}},{"unicode":8715,"advance":0.59999999999999998,"planeBounds":{"left":-0.15903846153846143,"bottom":-0.13999999999999993,"right":0.76403846153846167,"top":0.85999999999999999},"atlasBounds":{"left":185.5,"bottom":100.5,"right":197.5,"top":113.5}},{"unicode":8716,"advance":0.59999999999999998,"planeBounds":{"left":-0.15903846153846143,"bottom":-0.25538461538461527,"right":0.76403846153846167,"top":0.97538461538461563},"atlasBounds":{"left":303.5,"bottom":255.5,"right":315.5,"top":271.5}},{"unicode":8718,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384495,"bottom":-0.20653846153846148,"right":0.68461538461538463,"top":0.71653846153846168},"atlasBounds":{"left":89.5,"bottom":87.5,"right":99.5,"top":99.5}},{"unicode":8719,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.37884615384615383,"right":0.76153846153846172,"top":0.92884615384615399},"atlasBounds":{"left":492.5,"bottom":324.5,"right":504.5,"top":341.5}},{"unicode":8720,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.37884615384615383,"right":0.76153846153846172,"top":0.92884615384615399},"atlasBounds":{"left":479.5,"bottom":324.5,"right":491.5,"top":341.5}},{"unicode":8721,"advance":0.59999999999999998,"planeBounds":{"left":-0.16403846153846144,"bottom":-0.37884615384615383,"right":0.75903846153846166,"top":0.92884615384615399},"atlasBounds":{"left":466.5,"bottom":324.5,"right":478.5,"top":341.5}},{"unicode":8722,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384565,"bottom":0.060769230769230839,"right":0.68461538461538463,"top":0.59923076923076934},"atlasBounds":{"left":0.5,"bottom":47.5,"right":10.5,"top":54.5}},{"unicode":8725,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":50.5,"bottom":174.5,"right":61.5,"top":189.5}},{"unicode":8728,"advance":0.59999999999999998,"planeBounds":{"left":-0.046153846153846052,"bottom":-0.016153846153846053,"right":0.64615384615384619,"top":0.67615384615384622},"atlasBounds":{"left":417.5,"bottom":77.5,"right":426.5,"top":86.5}},{"unicode":8729,"advance":0.59999999999999998,"planeBounds":{"left":-0.0076923076923076155,"bottom":0.015307692307692385,"right":0.60769230769230775,"top":0.63069230769230777},"atlasBounds":{"left":62.5,"bottom":56.5,"right":70.5,"top":64.5}},{"unicode":8730,"advance":0.59999999999999998,"planeBounds":{"left":-0.19249999999999989,"bottom":-0.2119230769230768,"right":0.8075,"top":0.94192307692307697},"atlasBounds":{"left":345.5,"bottom":158.5,"right":358.5,"top":173.5}},{"unicode":8734,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.052115384615384522,"right":0.76153846153846172,"top":0.71711538461538471},"atlasBounds":{"left":256.5,"bottom":76.5,"right":268.5,"top":86.5}},{"unicode":8739,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230823,"bottom":-0.2119230769230768,"right":0.56923076923076932,"top":0.94192307692307697},"atlasBounds":{"left":504.5,"bottom":206.5,"right":511.5,"top":221.5}},{"unicode":8741,"advance":0.59999999999999998,"planeBounds":{"left":-0.046153846153846038,"bottom":-0.2119230769230768,"right":0.64615384615384619,"top":0.94192307692307697},"atlasBounds":{"left":200.5,"bottom":158.5,"right":209.5,"top":173.5}},{"unicode":8743,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":-0.16153846153846144,"right":0.76153846153846172,"top":0.76153846153846172},"atlasBounds":{"left":494.5,"bottom":101.5,"right":506.5,"top":113.5}},{"unicode":8744,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":-0.16153846153846144,"right":0.76153846153846172,"top":0.76153846153846172},"atlasBounds":{"left":10.5,"bottom":87.5,"right":22.5,"top":99.5}},{"unicode":8745,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":98.5,"bottom":158.5,"right":110.5,"top":173.5}},{"unicode":8746,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":139.5,"bottom":174.5,"right":151.5,"top":189.5}},{"unicode":8747,"advance":0.59999999999999998,"planeBounds":{"left":-0.19403846153846144,"bottom":-0.37884615384615383,"right":0.72903846153846164,"top":0.92884615384615399},"atlasBounds":{"left":303.5,"bottom":324.5,"right":315.5,"top":341.5}},{"unicode":8758,"advance":0.59999999999999998,"planeBounds":{"left":-0.0076923076923076155,"bottom":-0.22499999999999989,"right":0.60769230769230775,"top":0.77500000000000002},"atlasBounds":{"left":503.5,"bottom":129.5,"right":511.5,"top":142.5}},{"unicode":8759,"advance":0.59999999999999998,"planeBounds":{"left":-0.16203846153846144,"bottom":-0.21153846153846143,"right":0.76103846153846166,"top":0.71153846153846168},"atlasBounds":{"left":100.5,"bottom":87.5,"right":112.5,"top":99.5}},{"unicode":8760,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384565,"bottom":-0.054615384615384552,"right":0.68461538461538463,"top":0.71461538461538465},"atlasBounds":{"left":348.5,"bottom":76.5,"right":358.5,"top":86.5}},{"unicode":8761,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.093076923076922988,"right":0.80000000000000004,"top":0.75307692307692309},"atlasBounds":{"left":462.5,"bottom":88.5,"right":475.5,"top":99.5}},{"unicode":8764,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":0.044807692307692402,"right":0.72307692307692306,"top":0.66019230769230774},"atlasBounds":{"left":224.5,"bottom":56.5,"right":235.5,"top":64.5}},{"unicode":8771,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.053576923076922973,"right":0.72307692307692306,"top":0.79257692307692307},"atlasBounds":{"left":36.5,"bottom":75.5,"right":47.5,"top":86.5}},{"unicode":8773,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.12699999999999989,"right":0.72307692307692306,"top":0.873},"atlasBounds":{"left":79.5,"bottom":100.5,"right":90.5,"top":113.5}},{"unicode":8775,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.31384615384615372,"right":0.72307692307692306,"top":0.99384615384615405},"atlasBounds":{"left":205.5,"bottom":324.5,"right":216.5,"top":341.5}},{"unicode":8776,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.093076923076922988,"right":0.72307692307692306,"top":0.75307692307692309},"atlasBounds":{"left":48.5,"bottom":75.5,"right":59.5,"top":86.5}},{"unicode":8777,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.25538461538461527,"right":0.72307692307692306,"top":0.97538461538461563},"atlasBounds":{"left":431.5,"bottom":289.5,"right":442.5,"top":305.5}},{"unicode":8779,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.16949999999999996,"right":0.76153846153846172,"top":0.83050000000000002},"atlasBounds":{"left":339.5,"bottom":100.5,"right":351.5,"top":113.5}},{"unicode":8788,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.093076923076923015,"right":0.80000000000000004,"top":0.75307692307692309},"atlasBounds":{"left":292.5,"bottom":88.5,"right":305.5,"top":99.5}},{"unicode":8791,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.13996153846153833,"right":0.72307692307692306,"top":0.93696153846153851},"atlasBounds":{"left":267.5,"bottom":143.5,"right":278.5,"top":157.5}},{"unicode":8799,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.14342307692307679,"right":0.72307692307692306,"top":1.0104230769230769},"atlasBounds":{"left":423.5,"bottom":190.5,"right":434.5,"top":205.5}},{"unicode":8800,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.25538461538461527,"right":0.72307692307692306,"top":0.97538461538461563},"atlasBounds":{"left":356.5,"bottom":289.5,"right":367.5,"top":305.5}},{"unicode":8801,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.13153846153846144,"right":0.72307692307692306,"top":0.79153846153846164},"atlasBounds":{"left":113.5,"bottom":87.5,"right":124.5,"top":99.5}},{"unicode":8802,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.28088461538461523,"right":0.72307692307692306,"top":0.94988461538461566},"atlasBounds":{"left":332.5,"bottom":289.5,"right":343.5,"top":305.5}},{"unicode":8804,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.21692307692307683,"right":0.72307692307692306,"top":0.93692307692307697},"atlasBounds":{"left":350.5,"bottom":190.5,"right":361.5,"top":205.5}},{"unicode":8805,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.21692307692307683,"right":0.72307692307692306,"top":0.93692307692307697},"atlasBounds":{"left":338.5,"bottom":190.5,"right":349.5,"top":205.5}},{"unicode":8814,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.28538461538461524,"right":0.72307692307692306,"top":0.9453846153846156},"atlasBounds":{"left":0.5,"bottom":289.5,"right":11.5,"top":305.5}},{"unicode":8815,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.28538461538461524,"right":0.72307692307692306,"top":0.9453846153846156},"atlasBounds":{"left":407.5,"bottom":307.5,"right":418.5,"top":323.5}},{"unicode":8816,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.31884615384615372,"right":0.72307692307692306,"top":0.98884615384615404},"atlasBounds":{"left":12.5,"bottom":324.5,"right":23.5,"top":341.5}},{"unicode":8817,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692296,"bottom":-0.31884615384615372,"right":0.72307692307692306,"top":0.98884615384615404},"atlasBounds":{"left":0.5,"bottom":324.5,"right":11.5,"top":341.5}},{"unicode":8818,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.21692307692307683,"right":0.76153846153846172,"top":0.93692307692307697},"atlasBounds":{"left":149.5,"bottom":190.5,"right":161.5,"top":205.5}},{"unicode":8819,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.21942307692307686,"right":0.72307692307692306,"top":0.93442307692307691},"atlasBounds":{"left":137.5,"bottom":190.5,"right":148.5,"top":205.5}},{"unicode":8826,"advance":0.59999999999999998,"planeBounds":{"left":-0.17403846153846145,"bottom":-0.17249999999999993,"right":0.74903846153846165,"top":0.82750000000000001},"atlasBounds":{"left":117.5,"bottom":100.5,"right":129.5,"top":113.5}},{"unicode":8828,"advance":0.59999999999999998,"planeBounds":{"left":-0.17403846153846145,"bottom":-0.21442307692307686,"right":0.74903846153846165,"top":0.93942307692307692},"atlasBounds":{"left":76.5,"bottom":190.5,"right":88.5,"top":205.5}},{"unicode":8834,"advance":0.59999999999999998,"planeBounds":{"left":-0.15903846153846143,"bottom":-0.10153846153846149,"right":0.76403846153846167,"top":0.82153846153846166},"atlasBounds":{"left":154.5,"bottom":87.5,"right":166.5,"top":99.5}},{"unicode":8835,"advance":0.59999999999999998,"planeBounds":{"left":-0.16403846153846144,"bottom":-0.10153846153846149,"right":0.75903846153846166,"top":0.82153846153846166},"atlasBounds":{"left":141.5,"bottom":87.5,"right":153.5,"top":99.5}},{"unicode":8836,"advance":0.59999999999999998,"planeBounds":{"left":-0.15903846153846143,"bottom":-0.25538461538461527,"right":0.76403846153846167,"top":0.97538461538461563},"atlasBounds":{"left":457.5,"bottom":307.5,"right":469.5,"top":323.5}},{"unicode":8837,"advance":0.59999999999999998,"planeBounds":{"left":-0.16403846153846144,"bottom":-0.25538461538461527,"right":0.75903846153846166,"top":0.97538461538461563},"atlasBounds":{"left":431.5,"bottom":307.5,"right":443.5,"top":323.5}},{"unicode":8838,"advance":0.59999999999999998,"planeBounds":{"left":-0.15903846153846143,"bottom":-0.19846153846153836,"right":0.76403846153846167,"top":0.87846153846153852},"atlasBounds":{"left":114.5,"bottom":143.5,"right":126.5,"top":157.5}},{"unicode":8839,"advance":0.59999999999999998,"planeBounds":{"left":-0.16403846153846144,"bottom":-0.19846153846153836,"right":0.75903846153846166,"top":0.87846153846153852},"atlasBounds":{"left":101.5,"bottom":143.5,"right":113.5,"top":157.5}},{"unicode":8840,"advance":0.59999999999999998,"planeBounds":{"left":-0.15903846153846143,"bottom":-0.31384615384615372,"right":0.76403846153846167,"top":0.99384615384615405},"atlasBounds":{"left":372.5,"bottom":343.5,"right":384.5,"top":360.5}},{"unicode":8846,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.2119230769230768,"right":0.72307692307692306,"top":0.94192307692307697},"atlasBounds":{"left":414.5,"bottom":222.5,"right":425.5,"top":237.5}},{"unicode":8849,"advance":0.59999999999999998,"planeBounds":{"left":-0.15903846153846143,"bottom":-0.19846153846153836,"right":0.76403846153846167,"top":0.87846153846153852},"atlasBounds":{"left":88.5,"bottom":143.5,"right":100.5,"top":157.5}},{"unicode":8851,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":-0.19403846153846144,"right":0.76153846153846172,"top":0.72903846153846164},"atlasBounds":{"left":481.5,"bottom":101.5,"right":493.5,"top":113.5}},{"unicode":8852,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":-0.19403846153846144,"right":0.76153846153846172,"top":0.72903846153846164},"atlasBounds":{"left":468.5,"bottom":101.5,"right":480.5,"top":113.5}},{"unicode":8853,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.20846153846153839,"right":0.83846153846153837,"top":0.8684615384615384},"atlasBounds":{"left":37.5,"bottom":143.5,"right":51.5,"top":157.5}},{"unicode":8854,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.20846153846153839,"right":0.83846153846153837,"top":0.8684615384615384},"atlasBounds":{"left":461.5,"bottom":159.5,"right":475.5,"top":173.5}},{"unicode":8855,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.20846153846153839,"right":0.83846153846153837,"top":0.8684615384615384},"atlasBounds":{"left":237.5,"bottom":143.5,"right":251.5,"top":157.5}},{"unicode":8857,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.22846153846153841,"right":0.83846153846153837,"top":0.84846153846153838},"atlasBounds":{"left":252.5,"bottom":143.5,"right":266.5,"top":157.5}},{"unicode":8859,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.20846153846153839,"right":0.83846153846153837,"top":0.8684615384615384},"atlasBounds":{"left":421.5,"bottom":143.5,"right":435.5,"top":157.5}},{"unicode":8866,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.19999999999999993,"right":0.80000000000000004,"top":0.80000000000000004},"atlasBounds":{"left":65.5,"bottom":100.5,"right":78.5,"top":113.5}},{"unicode":8868,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.19999999999999993,"right":0.80000000000000004,"top":0.80000000000000004},"atlasBounds":{"left":256.5,"bottom":114.5,"right":269.5,"top":127.5}},{"unicode":8869,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.19999999999999993,"right":0.80000000000000004,"top":0.80000000000000004},"atlasBounds":{"left":242.5,"bottom":114.5,"right":255.5,"top":127.5}},{"unicode":8888,"advance":0.59999999999999998,"planeBounds":{"left":-0.1949999999999999,"bottom":0.022307692307692393,"right":0.80500000000000005,"top":0.63769230769230778},"atlasBounds":{"left":262.5,"bottom":56.5,"right":275.5,"top":64.5}},{"unicode":8902,"advance":0.59999999999999998,"planeBounds":{"left":-0.046153846153846052,"bottom":-0.0051538461538460827,"right":0.64615384615384619,"top":0.68715384615384623},"atlasBounds":{"left":389.5,"bottom":65.5,"right":398.5,"top":74.5}},{"unicode":8910,"advance":0.59999999999999998,"planeBounds":{"left":-0.20049999999999993,"bottom":-0.22299999999999989,"right":0.79949999999999999,"top":0.77700000000000002},"atlasBounds":{"left":89.5,"bottom":114.5,"right":102.5,"top":127.5}},{"unicode":8912,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846146,"bottom":-0.13999999999999993,"right":0.76153846153846172,"top":0.85999999999999999},"atlasBounds":{"left":66.5,"bottom":114.5,"right":78.5,"top":127.5}},{"unicode":8942,"advance":0.59999999999999998,"planeBounds":{"left":0.031269230769230869,"bottom":-0.20446153846153839,"right":0.56973076923076926,"top":0.8724615384615384},"atlasBounds":{"left":472.5,"bottom":143.5,"right":479.5,"top":157.5}},{"unicode":8943,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":0.064769230769230829,"right":0.83846153846153837,"top":0.60323076923076935},"atlasBounds":{"left":97.5,"bottom":47.5,"right":111.5,"top":54.5}},{"unicode":8944,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.20446153846153839,"right":0.83846153846153837,"top":0.8724615384615384},"atlasBounds":{"left":0.5,"bottom":128.5,"right":14.5,"top":142.5}},{"unicode":8945,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.20446153846153839,"right":0.83846153846153837,"top":0.8724615384615384},"atlasBounds":{"left":15.5,"bottom":128.5,"right":29.5,"top":142.5}},{"unicode":8962,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":-0.21999999999999989,"right":0.72307692307692306,"top":0.78000000000000003},"atlasBounds":{"left":354.5,"bottom":129.5,"right":365.5,"top":142.5}},{"unicode":8963,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":0.15230769230769242,"right":0.76153846153846172,"top":0.76769230769230778},"atlasBounds":{"left":315.5,"bottom":56.5,"right":327.5,"top":64.5}},{"unicode":8964,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.21861538461538454,"right":0.80000000000000004,"top":0.55061538461538462},"atlasBounds":{"left":149.5,"bottom":76.5,"right":162.5,"top":86.5}},{"unicode":8965,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":0.0053846153846154512,"right":0.76153846153846172,"top":0.77461538461538471},"atlasBounds":{"left":72.5,"bottom":76.5,"right":84.5,"top":86.5}},{"unicode":8968,"advance":0.59999999999999998,"planeBounds":{"left":-0.01865384615384607,"bottom":-0.33230769230769225,"right":0.67365384615384616,"top":1.0523076923076924},"atlasBounds":{"left":66.5,"bottom":342.5,"right":75.5,"top":360.5}},{"unicode":8969,"advance":0.59999999999999998,"planeBounds":{"left":-0.07365384615384607,"bottom":-0.33230769230769225,"right":0.61865384615384622,"top":1.0523076923076924},"atlasBounds":{"left":56.5,"bottom":342.5,"right":65.5,"top":360.5}},{"unicode":8970,"advance":0.59999999999999998,"planeBounds":{"left":-0.01865384615384607,"bottom":-0.33230769230769225,"right":0.67365384615384616,"top":1.0523076923076924},"atlasBounds":{"left":46.5,"bottom":342.5,"right":55.5,"top":360.5}},{"unicode":8971,"advance":0.59999999999999998,"planeBounds":{"left":-0.07365384615384607,"bottom":-0.33230769230769225,"right":0.61865384615384622,"top":1.0523076923076924},"atlasBounds":{"left":36.5,"bottom":342.5,"right":45.5,"top":360.5}},{"unicode":8984,"advance":0.59999999999999998,"planeBounds":{"left":-0.1999999999999999,"bottom":-0.22499999999999989,"right":0.80000000000000004,"top":0.77500000000000002},"atlasBounds":{"left":73.5,"bottom":129.5,"right":86.5,"top":142.5}},{"unicode":8997,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.22499999999999995,"right":0.80000000000000004,"top":0.77500000000000002},"atlasBounds":{"left":44.5,"bottom":129.5,"right":57.5,"top":142.5}},{"unicode":9064,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":0.045423076923077017,"right":0.72307692307692306,"top":0.89157692307692316},"atlasBounds":{"left":337.5,"bottom":88.5,"right":348.5,"top":99.5}},{"unicode":9075,"advance":0.59999999999999998,"planeBounds":{"left":-0.15903846153846146,"bottom":-0.22499999999999995,"right":0.76403846153846167,"top":0.77500000000000002},"atlasBounds":{"left":130.5,"bottom":100.5,"right":142.5,"top":113.5}},{"unicode":9076,"advance":0.59999999999999998,"planeBounds":{"left":-0.12107692307692298,"bottom":-0.38692307692307687,"right":0.72507692307692306,"top":0.76692307692307693},"atlasBounds":{"left":321.5,"bottom":158.5,"right":332.5,"top":173.5}},{"unicode":9115,"advance":0.59999999999999998,"planeBounds":{"left":0.058846153846153916,"bottom":-0.49923076923076909,"right":0.75115384615384617,"top":1.0392307692307696},"atlasBounds":{"left":31.5,"bottom":439.5,"right":40.5,"top":459.5}},{"unicode":9116,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230823,"bottom":-0.52461538461538448,"right":0.56923076923076932,"top":1.2446153846153847},"atlasBounds":{"left":88.5,"bottom":462.5,"right":95.5,"top":485.5}},{"unicode":9117,"advance":0.59999999999999998,"planeBounds":{"left":0.058846153846153916,"bottom":-0.3192307692307691,"right":0.75115384615384617,"top":1.2192307692307693},"atlasBounds":{"left":41.5,"bottom":439.5,"right":50.5,"top":459.5}},{"unicode":9118,"advance":0.59999999999999998,"planeBounds":{"left":-0.15115384615384608,"bottom":-0.49923076923076909,"right":0.54115384615384621,"top":1.0392307692307696},"atlasBounds":{"left":51.5,"bottom":439.5,"right":60.5,"top":459.5}},{"unicode":9119,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230823,"bottom":-0.52461538461538448,"right":0.56923076923076932,"top":1.2446153846153847},"atlasBounds":{"left":96.5,"bottom":462.5,"right":103.5,"top":485.5}},{"unicode":9120,"advance":0.59999999999999998,"planeBounds":{"left":-0.15115384615384608,"bottom":-0.3192307692307691,"right":0.54115384615384621,"top":1.2192307692307693},"atlasBounds":{"left":61.5,"bottom":439.5,"right":70.5,"top":459.5}},{"unicode":9121,"advance":0.59999999999999998,"planeBounds":{"left":0.031346153846153933,"bottom":-0.50423076923076904,"right":0.7236538461538462,"top":1.0342307692307695},"atlasBounds":{"left":71.5,"bottom":439.5,"right":80.5,"top":459.5}},{"unicode":9122,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230823,"bottom":-0.52461538461538448,"right":0.56923076923076932,"top":1.2446153846153847},"atlasBounds":{"left":104.5,"bottom":462.5,"right":111.5,"top":485.5}},{"unicode":9123,"advance":0.59999999999999998,"planeBounds":{"left":0.031346153846153933,"bottom":-0.31423076923076904,"right":0.7236538461538462,"top":1.2242307692307695},"atlasBounds":{"left":81.5,"bottom":439.5,"right":90.5,"top":459.5}},{"unicode":9124,"advance":0.59999999999999998,"planeBounds":{"left":-0.12365384615384607,"bottom":-0.50423076923076904,"right":0.56865384615384618,"top":1.0342307692307695},"atlasBounds":{"left":91.5,"bottom":439.5,"right":100.5,"top":459.5}},{"unicode":9125,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230823,"bottom":-0.52461538461538448,"right":0.56923076923076932,"top":1.2446153846153847},"atlasBounds":{"left":112.5,"bottom":462.5,"right":119.5,"top":485.5}},{"unicode":9126,"advance":0.59999999999999998,"planeBounds":{"left":-0.12365384615384607,"bottom":-0.31423076923076904,"right":0.56865384615384618,"top":1.2242307692307695},"atlasBounds":{"left":101.5,"bottom":439.5,"right":110.5,"top":459.5}},{"unicode":9127,"advance":0.59999999999999998,"planeBounds":{"left":0.062307692307692404,"bottom":-0.50423076923076904,"right":0.6776923076923077,"top":1.0342307692307695},"atlasBounds":{"left":111.5,"bottom":439.5,"right":119.5,"top":459.5}},{"unicode":9128,"advance":0.59999999999999998,"planeBounds":{"left":-0.15115384615384608,"bottom":-0.52461538461538448,"right":0.54115384615384621,"top":1.2446153846153847},"atlasBounds":{"left":120.5,"bottom":462.5,"right":129.5,"top":485.5}},{"unicode":9129,"advance":0.59999999999999998,"planeBounds":{"left":0.062307692307692404,"bottom":-0.31423076923076904,"right":0.6776923076923077,"top":1.2242307692307695},"atlasBounds":{"left":120.5,"bottom":439.5,"right":128.5,"top":459.5}},{"unicode":9130,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230823,"bottom":-0.52461538461538448,"right":0.56923076923076932,"top":1.2446153846153847},"atlasBounds":{"left":130.5,"bottom":462.5,"right":137.5,"top":485.5}},{"unicode":9131,"advance":0.59999999999999998,"planeBounds":{"left":-0.077692307692307602,"bottom":-0.50423076923076904,"right":0.5376923076923078,"top":1.0342307692307695},"atlasBounds":{"left":129.5,"bottom":439.5,"right":137.5,"top":459.5}},{"unicode":9132,"advance":0.59999999999999998,"planeBounds":{"left":0.058846153846153916,"bottom":-0.52461538461538448,"right":0.75115384615384617,"top":1.2446153846153847},"atlasBounds":{"left":138.5,"bottom":462.5,"right":147.5,"top":485.5}},{"unicode":9133,"advance":0.59999999999999998,"planeBounds":{"left":-0.077692307692307602,"bottom":-0.31423076923076904,"right":0.5376923076923078,"top":1.2242307692307695},"atlasBounds":{"left":138.5,"bottom":439.5,"right":146.5,"top":459.5}},{"unicode":9211,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.2194230769230768,"right":0.83846153846153848,"top":0.93442307692307691},"atlasBounds":{"left":231.5,"bottom":239.5,"right":245.5,"top":254.5}},{"unicode":9212,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.23346153846153833,"right":0.83846153846153848,"top":0.84346153846153848},"atlasBounds":{"left":426.5,"bottom":159.5,"right":440.5,"top":173.5}},{"unicode":9213,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230816,"bottom":-0.23346153846153839,"right":0.56923076923076932,"top":0.84346153846153837},"atlasBounds":{"left":441.5,"bottom":159.5,"right":448.5,"top":173.5}},{"unicode":9214,"advance":0.59999999999999998,"planeBounds":{"left":-0.18999999999999989,"bottom":-0.20452083333333326,"right":0.81000000000000005,"top":0.79547916666666663},"atlasBounds":{"left":264.5,"bottom":129.5,"right":277.5,"top":142.5}},{"unicode":9216,"advance":0.59999999999999998,"planeBounds":{"left":-0.19299999999999992,"bottom":0.017846153846153932,"right":0.80700000000000005,"top":0.71015384615384625},"atlasBounds":{"left":351.5,"bottom":65.5,"right":364.5,"top":74.5}},{"unicode":9217,"advance":0.59999999999999998,"planeBounds":{"left":-0.2034999999999999,"bottom":0.019346153846153919,"right":0.79649999999999999,"top":0.71165384615384619},"atlasBounds":{"left":337.5,"bottom":65.5,"right":350.5,"top":74.5}},{"unicode":9218,"advance":0.59999999999999998,"planeBounds":{"left":-0.1944999999999999,"bottom":0.019346153846153919,"right":0.80549999999999999,"top":0.71165384615384619},"atlasBounds":{"left":0.5,"bottom":55.5,"right":13.5,"top":64.5}},{"unicode":9219,"advance":0.59999999999999998,"planeBounds":{"left":-0.18999999999999992,"bottom":0.019346153846153947,"right":0.81000000000000005,"top":0.71165384615384619},"atlasBounds":{"left":102.5,"bottom":65.5,"right":115.5,"top":74.5}},{"unicode":9220,"advance":0.59999999999999998,"planeBounds":{"left":-0.19249999999999989,"bottom":0.019346153846153919,"right":0.8075,"top":0.71165384615384619},"atlasBounds":{"left":88.5,"bottom":65.5,"right":101.5,"top":74.5}},{"unicode":9221,"advance":0.59999999999999998,"planeBounds":{"left":-0.19549999999999995,"bottom":-0.0086538461538460971,"right":0.80449999999999999,"top":0.68365384615384617},"atlasBounds":{"left":63.5,"bottom":65.5,"right":76.5,"top":74.5}},{"unicode":9222,"advance":0.59999999999999998,"planeBounds":{"left":-0.19799999999999993,"bottom":0.019346153846153919,"right":0.80200000000000005,"top":0.71165384615384619},"atlasBounds":{"left":472.5,"bottom":77.5,"right":485.5,"top":86.5}},{"unicode":9223,"advance":0.59999999999999998,"planeBounds":{"left":-0.19249999999999992,"bottom":0.019346153846153947,"right":0.8075,"top":0.71165384615384619},"atlasBounds":{"left":452.5,"bottom":65.5,"right":465.5,"top":74.5}},{"unicode":9224,"advance":0.59999999999999998,"planeBounds":{"left":-0.081615384615384534,"bottom":0.019346153846153919,"right":0.68761538461538463,"top":0.71165384615384619},"atlasBounds":{"left":234.5,"bottom":65.5,"right":244.5,"top":74.5}},{"unicode":9225,"advance":0.59999999999999998,"planeBounds":{"left":-0.078615384615384531,"bottom":0.019346153846153947,"right":0.69061538461538463,"top":0.71165384615384619},"atlasBounds":{"left":0.5,"bottom":65.5,"right":10.5,"top":74.5}},{"unicode":9226,"advance":0.59999999999999998,"planeBounds":{"left":-0.076115384615384529,"bottom":0.019346153846153947,"right":0.69311538461538469,"top":0.71165384615384619},"atlasBounds":{"left":173.5,"bottom":65.5,"right":183.5,"top":74.5}},{"unicode":9227,"advance":0.59999999999999998,"planeBounds":{"left":-0.085615384615384524,"bottom":0.019346153846153947,"right":0.68361538461538462,"top":0.71165384615384619},"atlasBounds":{"left":140.5,"bottom":65.5,"right":150.5,"top":74.5}},{"unicode":9228,"advance":0.59999999999999998,"planeBounds":{"left":-0.082115384615384535,"bottom":0.019346153846153947,"right":0.68711538461538468,"top":0.71165384615384619},"atlasBounds":{"left":77.5,"bottom":65.5,"right":87.5,"top":74.5}},{"unicode":9229,"advance":0.59999999999999998,"planeBounds":{"left":-0.07961538461538456,"bottom":0.019346153846153919,"right":0.68961538461538463,"top":0.71165384615384619},"atlasBounds":{"left":477.5,"bottom":65.5,"right":487.5,"top":74.5}},{"unicode":9230,"advance":0.59999999999999998,"planeBounds":{"left":-0.087615384615384526,"bottom":0.019346153846153919,"right":0.68161538461538462,"top":0.71165384615384619},"atlasBounds":{"left":450.5,"bottom":77.5,"right":460.5,"top":86.5}},{"unicode":9231,"advance":0.59999999999999998,"planeBounds":{"left":-0.090615384615384501,"bottom":0.019346153846153919,"right":0.67861538461538462,"top":0.71165384615384619},"atlasBounds":{"left":245.5,"bottom":65.5,"right":255.5,"top":74.5}},{"unicode":9232,"advance":0.59999999999999998,"planeBounds":{"left":-0.1979999999999999,"bottom":0.019346153846153947,"right":0.80200000000000005,"top":0.71165384615384619},"atlasBounds":{"left":393.5,"bottom":77.5,"right":406.5,"top":86.5}},{"unicode":9233,"advance":0.59999999999999998,"planeBounds":{"left":-0.1949999999999999,"bottom":0.019346153846153919,"right":0.80500000000000005,"top":0.71165384615384619},"atlasBounds":{"left":427.5,"bottom":77.5,"right":440.5,"top":86.5}},{"unicode":9234,"advance":0.59999999999999998,"planeBounds":{"left":-0.1969999999999999,"bottom":0.019346153846153919,"right":0.80300000000000005,"top":0.71165384615384619},"atlasBounds":{"left":116.5,"bottom":65.5,"right":129.5,"top":74.5}},{"unicode":9235,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999996,"bottom":0.019346153846153919,"right":0.80000000000000004,"top":0.71165384615384619},"atlasBounds":{"left":278.5,"bottom":65.5,"right":291.5,"top":74.5}},{"unicode":9236,"advance":0.59999999999999998,"planeBounds":{"left":-0.2029999999999999,"bottom":0.019346153846153919,"right":0.79700000000000004,"top":0.71165384615384619},"atlasBounds":{"left":488.5,"bottom":65.5,"right":501.5,"top":74.5}},{"unicode":9237,"advance":0.59999999999999998,"planeBounds":{"left":-0.19149999999999992,"bottom":0.019346153846153947,"right":0.8085,"top":0.71165384615384619},"atlasBounds":{"left":11.5,"bottom":65.5,"right":24.5,"top":74.5}},{"unicode":9238,"advance":0.59999999999999998,"planeBounds":{"left":-0.2029999999999999,"bottom":0.019346153846153919,"right":0.79700000000000004,"top":0.71165384615384619},"atlasBounds":{"left":39.5,"bottom":65.5,"right":52.5,"top":74.5}},{"unicode":9239,"advance":0.59999999999999998,"planeBounds":{"left":-0.1949999999999999,"bottom":0.019346153846153947,"right":0.80500000000000005,"top":0.71165384615384619},"atlasBounds":{"left":359.5,"bottom":77.5,"right":372.5,"top":86.5}},{"unicode":9240,"advance":0.59999999999999998,"planeBounds":{"left":-0.20049999999999996,"bottom":0.019346153846153919,"right":0.79949999999999999,"top":0.71165384615384619},"atlasBounds":{"left":25.5,"bottom":65.5,"right":38.5,"top":74.5}},{"unicode":9241,"advance":0.59999999999999998,"planeBounds":{"left":-0.081115384615384506,"bottom":0.019346153846153947,"right":0.68811538461538468,"top":0.71165384615384619},"atlasBounds":{"left":151.5,"bottom":65.5,"right":161.5,"top":74.5}},{"unicode":9242,"advance":0.59999999999999998,"planeBounds":{"left":-0.19949999999999993,"bottom":0.019346153846153919,"right":0.80049999999999999,"top":0.71165384615384619},"atlasBounds":{"left":184.5,"bottom":65.5,"right":197.5,"top":74.5}},{"unicode":9243,"advance":0.59999999999999998,"planeBounds":{"left":-0.19699999999999995,"bottom":0.019346153846153919,"right":0.80300000000000005,"top":0.71165384615384619},"atlasBounds":{"left":198.5,"bottom":65.5,"right":211.5,"top":74.5}},{"unicode":9244,"advance":0.59999999999999998,"planeBounds":{"left":-0.081615384615384534,"bottom":0.019346153846153919,"right":0.68761538461538463,"top":0.71165384615384619},"atlasBounds":{"left":223.5,"bottom":65.5,"right":233.5,"top":74.5}},{"unicode":9245,"advance":0.59999999999999998,"planeBounds":{"left":-0.082615384615384535,"bottom":0.019346153846153919,"right":0.68661538461538463,"top":0.71165384615384619},"atlasBounds":{"left":256.5,"bottom":65.5,"right":266.5,"top":74.5}},{"unicode":9246,"advance":0.59999999999999998,"planeBounds":{"left":-0.082115384615384535,"bottom":0.019346153846153919,"right":0.68711538461538468,"top":0.71165384615384619},"atlasBounds":{"left":267.5,"bottom":65.5,"right":277.5,"top":74.5}},{"unicode":9247,"advance":0.59999999999999998,"planeBounds":{"left":-0.082115384615384535,"bottom":0.019346153846153919,"right":0.68711538461538468,"top":0.71165384615384619},"atlasBounds":{"left":292.5,"bottom":65.5,"right":302.5,"top":74.5}},{"unicode":9248,"advance":0.59999999999999998,"planeBounds":{"left":-0.081115384615384506,"bottom":0.019346153846153919,"right":0.68811538461538468,"top":0.71165384615384619},"atlasBounds":{"left":399.5,"bottom":65.5,"right":409.5,"top":74.5}},{"unicode":9249,"advance":0.59999999999999998,"planeBounds":{"left":-0.19299999999999992,"bottom":0.019346153846153947,"right":0.80700000000000005,"top":0.71165384615384619},"atlasBounds":{"left":438.5,"bottom":65.5,"right":451.5,"top":74.5}},{"unicode":9252,"advance":0.59999999999999998,"planeBounds":{"left":-0.078115384615384559,"bottom":0.019346153846153947,"right":0.69111538461538469,"top":0.71165384615384619},"atlasBounds":{"left":466.5,"bottom":65.5,"right":476.5,"top":74.5}},{"unicode":9472,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":0.10076923076923083,"right":0.83846153846153848,"top":0.63923076923076927},"atlasBounds":{"left":224.5,"bottom":47.5,"right":238.5,"top":54.5}},{"unicode":9473,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":0.062307692307692383,"right":0.83846153846153848,"top":0.6776923076923077},"atlasBounds":{"left":32.5,"bottom":56.5,"right":46.5,"top":64.5}},{"unicode":9474,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230823,"bottom":-0.60153846153846147,"right":0.56923076923076932,"top":1.3215384615384616},"atlasBounds":{"left":0.5,"bottom":486.5,"right":7.5,"top":511.5}},{"unicode":9475,"advance":0.59999999999999998,"planeBounds":{"left":-0.0076923076923076155,"bottom":-0.60153846153846147,"right":0.60769230769230775,"top":1.3215384615384616},"atlasBounds":{"left":8.5,"bottom":486.5,"right":16.5,"top":511.5}},{"unicode":9476,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":0.10076923076923083,"right":0.76153846153846172,"top":0.63923076923076927},"atlasBounds":{"left":403.5,"bottom":57.5,"right":415.5,"top":64.5}},{"unicode":9477,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":0.062307692307692383,"right":0.76153846153846172,"top":0.6776923076923077},"atlasBounds":{"left":178.5,"bottom":56.5,"right":190.5,"top":64.5}},{"unicode":9478,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230823,"bottom":-0.44819230769230756,"right":0.56923076923076932,"top":1.1671923076923079},"atlasBounds":{"left":0.5,"bottom":438.5,"right":7.5,"top":459.5}},{"unicode":9479,"advance":0.59999999999999998,"planeBounds":{"left":-0.0076923076923076155,"bottom":-0.44819230769230756,"right":0.60769230769230775,"top":1.1671923076923079},"atlasBounds":{"left":8.5,"bottom":438.5,"right":16.5,"top":459.5}},{"unicode":9480,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999996,"bottom":0.10076923076923083,"right":0.80000000000000004,"top":0.63923076923076927},"atlasBounds":{"left":83.5,"bottom":47.5,"right":96.5,"top":54.5}},{"unicode":9481,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999996,"bottom":0.062307692307692383,"right":0.80000000000000004,"top":0.6776923076923077},"atlasBounds":{"left":236.5,"bottom":56.5,"right":249.5,"top":64.5}},{"unicode":9482,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230823,"bottom":-0.39923076923076906,"right":0.56923076923076932,"top":1.1392307692307695},"atlasBounds":{"left":147.5,"bottom":439.5,"right":154.5,"top":459.5}},{"unicode":9483,"advance":0.59999999999999998,"planeBounds":{"left":-0.0076923076923076155,"bottom":-0.39923076923076906,"right":0.60769230769230775,"top":1.1392307692307695},"atlasBounds":{"left":155.5,"bottom":439.5,"right":163.5,"top":459.5}},{"unicode":9484,"advance":0.59999999999999998,"planeBounds":{"left":0.050384615384615458,"bottom":-0.60538461538461541,"right":0.81961538461538463,"top":0.62538461538461554},"atlasBounds":{"left":143.5,"bottom":272.5,"right":153.5,"top":288.5}},{"unicode":9485,"advance":0.59999999999999998,"planeBounds":{"left":0.050384615384615458,"bottom":-0.61884615384615382,"right":0.81961538461538463,"top":0.68884615384615389},"atlasBounds":{"left":328.5,"bottom":324.5,"right":338.5,"top":341.5}},{"unicode":9486,"advance":0.59999999999999998,"planeBounds":{"left":-0.013076923076922988,"bottom":-0.60538461538461541,"right":0.83307692307692316,"top":0.62538461538461554},"atlasBounds":{"left":94.5,"bottom":272.5,"right":105.5,"top":288.5}},{"unicode":9487,"advance":0.59999999999999998,"planeBounds":{"left":-0.013076923076922988,"bottom":-0.61884615384615382,"right":0.83307692307692316,"top":0.68884615384615389},"atlasBounds":{"left":316.5,"bottom":324.5,"right":327.5,"top":341.5}},{"unicode":9488,"advance":0.59999999999999998,"planeBounds":{"left":-0.21961538461538455,"bottom":-0.60538461538461541,"right":0.54961538461538462,"top":0.62538461538461554},"atlasBounds":{"left":71.5,"bottom":272.5,"right":81.5,"top":288.5}},{"unicode":9489,"advance":0.59999999999999998,"planeBounds":{"left":-0.21961538461538455,"bottom":-0.61884615384615382,"right":0.54961538461538462,"top":0.68884615384615389},"atlasBounds":{"left":263.5,"bottom":324.5,"right":273.5,"top":341.5}},{"unicode":9490,"advance":0.59999999999999998,"planeBounds":{"left":-0.23307692307692299,"bottom":-0.60538461538461541,"right":0.61307692307692307,"top":0.62538461538461554},"atlasBounds":{"left":59.5,"bottom":272.5,"right":70.5,"top":288.5}},{"unicode":9491,"advance":0.59999999999999998,"planeBounds":{"left":-0.23307692307692299,"bottom":-0.61884615384615382,"right":0.61307692307692307,"top":0.68884615384615389},"atlasBounds":{"left":251.5,"bottom":324.5,"right":262.5,"top":341.5}},{"unicode":9492,"advance":0.59999999999999998,"planeBounds":{"left":0.050384615384615458,"bottom":0.10461538461538476,"right":0.81961538461538463,"top":1.3353846153846158},"atlasBounds":{"left":48.5,"bottom":272.5,"right":58.5,"top":288.5}},{"unicode":9493,"advance":0.59999999999999998,"planeBounds":{"left":0.050384615384615458,"bottom":0.041153846153846312,"right":0.81961538461538463,"top":1.348846153846154},"atlasBounds":{"left":240.5,"bottom":324.5,"right":250.5,"top":341.5}},{"unicode":9494,"advance":0.59999999999999998,"planeBounds":{"left":-0.013076923076922988,"bottom":0.10461538461538476,"right":0.83307692307692316,"top":1.3353846153846158},"atlasBounds":{"left":36.5,"bottom":272.5,"right":47.5,"top":288.5}},{"unicode":9495,"advance":0.59999999999999998,"planeBounds":{"left":-0.013076923076922988,"bottom":0.041153846153846312,"right":0.83307692307692316,"top":1.348846153846154},"atlasBounds":{"left":228.5,"bottom":324.5,"right":239.5,"top":341.5}},{"unicode":9496,"advance":0.59999999999999998,"planeBounds":{"left":-0.21961538461538455,"bottom":0.10461538461538476,"right":0.54961538461538462,"top":1.3353846153846158},"atlasBounds":{"left":25.5,"bottom":272.5,"right":35.5,"top":288.5}},{"unicode":9497,"advance":0.59999999999999998,"planeBounds":{"left":-0.21961538461538455,"bottom":0.041153846153846312,"right":0.54961538461538462,"top":1.348846153846154},"atlasBounds":{"left":217.5,"bottom":324.5,"right":227.5,"top":341.5}},{"unicode":9498,"advance":0.59999999999999998,"planeBounds":{"left":-0.23307692307692299,"bottom":0.10461538461538476,"right":0.61307692307692307,"top":1.3353846153846158},"atlasBounds":{"left":13.5,"bottom":272.5,"right":24.5,"top":288.5}},{"unicode":9499,"advance":0.59999999999999998,"planeBounds":{"left":-0.23307692307692299,"bottom":0.041153846153846312,"right":0.61307692307692307,"top":1.348846153846154},"atlasBounds":{"left":193.5,"bottom":324.5,"right":204.5,"top":341.5}},{"unicode":9500,"advance":0.59999999999999998,"planeBounds":{"left":0.050384615384615458,"bottom":-0.60153846153846147,"right":0.81961538461538463,"top":1.3215384615384616},"atlasBounds":{"left":17.5,"bottom":486.5,"right":27.5,"top":511.5}},{"unicode":9501,"advance":0.59999999999999998,"planeBounds":{"left":0.050384615384615458,"bottom":-0.60153846153846147,"right":0.81961538461538463,"top":1.3215384615384616},"atlasBounds":{"left":28.5,"bottom":486.5,"right":38.5,"top":511.5}},{"unicode":9502,"advance":0.59999999999999998,"planeBounds":{"left":-0.013076923076922988,"bottom":-0.60153846153846147,"right":0.83307692307692316,"top":1.3215384615384616},"atlasBounds":{"left":39.5,"bottom":486.5,"right":50.5,"top":511.5}},{"unicode":9503,"advance":0.59999999999999998,"planeBounds":{"left":-0.013076923076922988,"bottom":-0.60153846153846147,"right":0.83307692307692316,"top":1.3215384615384616},"atlasBounds":{"left":51.5,"bottom":486.5,"right":62.5,"top":511.5}},{"unicode":9504,"advance":0.59999999999999998,"planeBounds":{"left":-0.013076923076922988,"bottom":-0.60153846153846147,"right":0.83307692307692316,"top":1.3215384615384616},"atlasBounds":{"left":63.5,"bottom":486.5,"right":74.5,"top":511.5}},{"unicode":9505,"advance":0.59999999999999998,"planeBounds":{"left":-0.013076923076922988,"bottom":-0.60153846153846147,"right":0.83307692307692316,"top":1.3215384615384616},"atlasBounds":{"left":75.5,"bottom":486.5,"right":86.5,"top":511.5}},{"unicode":9506,"advance":0.59999999999999998,"planeBounds":{"left":-0.013076923076922988,"bottom":-0.60153846153846147,"right":0.83307692307692316,"top":1.3215384615384616},"atlasBounds":{"left":87.5,"bottom":486.5,"right":98.5,"top":511.5}},{"unicode":9507,"advance":0.59999999999999998,"planeBounds":{"left":-0.013076923076922988,"bottom":-0.60153846153846147,"right":0.83307692307692316,"top":1.3215384615384616},"atlasBounds":{"left":99.5,"bottom":486.5,"right":110.5,"top":511.5}},{"unicode":9508,"advance":0.59999999999999998,"planeBounds":{"left":-0.21961538461538455,"bottom":-0.60153846153846147,"right":0.54961538461538462,"top":1.3215384615384616},"atlasBounds":{"left":111.5,"bottom":486.5,"right":121.5,"top":511.5}},{"unicode":9509,"advance":0.59999999999999998,"planeBounds":{"left":-0.21961538461538455,"bottom":-0.60153846153846147,"right":0.54961538461538462,"top":1.3215384615384616},"atlasBounds":{"left":122.5,"bottom":486.5,"right":132.5,"top":511.5}},{"unicode":9510,"advance":0.59999999999999998,"planeBounds":{"left":-0.23307692307692299,"bottom":-0.60153846153846147,"right":0.61307692307692307,"top":1.3215384615384616},"atlasBounds":{"left":133.5,"bottom":486.5,"right":144.5,"top":511.5}},{"unicode":9511,"advance":0.59999999999999998,"planeBounds":{"left":-0.23307692307692299,"bottom":-0.61307692307692296,"right":0.61307692307692307,"top":1.2330769230769232},"atlasBounds":{"left":76.5,"bottom":461.5,"right":87.5,"top":485.5}},{"unicode":9512,"advance":0.59999999999999998,"planeBounds":{"left":-0.23307692307692299,"bottom":-0.60153846153846147,"right":0.61307692307692307,"top":1.3215384615384616},"atlasBounds":{"left":145.5,"bottom":486.5,"right":156.5,"top":511.5}},{"unicode":9513,"advance":0.59999999999999998,"planeBounds":{"left":-0.23307692307692299,"bottom":-0.60153846153846147,"right":0.61307692307692307,"top":1.3215384615384616},"atlasBounds":{"left":157.5,"bottom":486.5,"right":168.5,"top":511.5}},{"unicode":9514,"advance":0.59999999999999998,"planeBounds":{"left":-0.23307692307692299,"bottom":-0.60153846153846147,"right":0.61307692307692307,"top":1.3215384615384616},"atlasBounds":{"left":169.5,"bottom":486.5,"right":180.5,"top":511.5}},{"unicode":9515,"advance":0.59999999999999998,"planeBounds":{"left":-0.23307692307692299,"bottom":-0.60153846153846147,"right":0.61307692307692307,"top":1.3215384615384616},"atlasBounds":{"left":181.5,"bottom":486.5,"right":192.5,"top":511.5}},{"unicode":9516,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60538461538461541,"right":0.83846153846153848,"top":0.62538461538461554},"atlasBounds":{"left":443.5,"bottom":289.5,"right":457.5,"top":305.5}},{"unicode":9517,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.61884615384615382,"right":0.83846153846153848,"top":0.68884615384615389},"atlasBounds":{"left":154.5,"bottom":324.5,"right":168.5,"top":341.5}},{"unicode":9518,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.61884615384615382,"right":0.83846153846153848,"top":0.68884615384615389},"atlasBounds":{"left":139.5,"bottom":324.5,"right":153.5,"top":341.5}},{"unicode":9519,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.61884615384615382,"right":0.83846153846153848,"top":0.68884615384615389},"atlasBounds":{"left":124.5,"bottom":324.5,"right":138.5,"top":341.5}},{"unicode":9520,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60538461538461541,"right":0.83846153846153848,"top":0.62538461538461554},"atlasBounds":{"left":380.5,"bottom":289.5,"right":394.5,"top":305.5}},{"unicode":9521,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.61884615384615382,"right":0.83846153846153848,"top":0.68884615384615389},"atlasBounds":{"left":109.5,"bottom":324.5,"right":123.5,"top":341.5}},{"unicode":9522,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.61884615384615382,"right":0.83846153846153848,"top":0.68884615384615389},"atlasBounds":{"left":94.5,"bottom":324.5,"right":108.5,"top":341.5}},{"unicode":9523,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.61884615384615382,"right":0.83846153846153848,"top":0.68884615384615389},"atlasBounds":{"left":79.5,"bottom":324.5,"right":93.5,"top":341.5}},{"unicode":9524,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":0.10461538461538476,"right":0.83846153846153848,"top":1.3353846153846158},"atlasBounds":{"left":317.5,"bottom":289.5,"right":331.5,"top":305.5}},{"unicode":9525,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":0.041153846153846312,"right":0.83846153846153848,"top":1.348846153846154},"atlasBounds":{"left":39.5,"bottom":324.5,"right":53.5,"top":341.5}},{"unicode":9526,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":0.041153846153846312,"right":0.83846153846153848,"top":1.348846153846154},"atlasBounds":{"left":24.5,"bottom":324.5,"right":38.5,"top":341.5}},{"unicode":9527,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":0.041153846153846312,"right":0.83846153846153848,"top":1.348846153846154},"atlasBounds":{"left":489.5,"bottom":343.5,"right":503.5,"top":360.5}},{"unicode":9528,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":0.10461538461538476,"right":0.83846153846153848,"top":1.3353846153846158},"atlasBounds":{"left":496.5,"bottom":307.5,"right":510.5,"top":323.5}},{"unicode":9529,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":0.041153846153846312,"right":0.83846153846153848,"top":1.348846153846154},"atlasBounds":{"left":474.5,"bottom":343.5,"right":488.5,"top":360.5}},{"unicode":9530,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":0.041153846153846312,"right":0.83846153846153848,"top":1.348846153846154},"atlasBounds":{"left":459.5,"bottom":343.5,"right":473.5,"top":360.5}},{"unicode":9531,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":0.041153846153846312,"right":0.83846153846153848,"top":1.348846153846154},"atlasBounds":{"left":444.5,"bottom":343.5,"right":458.5,"top":360.5}},{"unicode":9532,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":193.5,"bottom":486.5,"right":207.5,"top":511.5}},{"unicode":9533,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":208.5,"bottom":486.5,"right":222.5,"top":511.5}},{"unicode":9534,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":223.5,"bottom":486.5,"right":237.5,"top":511.5}},{"unicode":9535,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":238.5,"bottom":486.5,"right":252.5,"top":511.5}},{"unicode":9536,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":253.5,"bottom":486.5,"right":267.5,"top":511.5}},{"unicode":9537,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":268.5,"bottom":486.5,"right":282.5,"top":511.5}},{"unicode":9538,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":283.5,"bottom":486.5,"right":297.5,"top":511.5}},{"unicode":9539,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":298.5,"bottom":486.5,"right":312.5,"top":511.5}},{"unicode":9540,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":313.5,"bottom":486.5,"right":327.5,"top":511.5}},{"unicode":9541,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":328.5,"bottom":486.5,"right":342.5,"top":511.5}},{"unicode":9542,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":343.5,"bottom":486.5,"right":357.5,"top":511.5}},{"unicode":9543,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":358.5,"bottom":486.5,"right":372.5,"top":511.5}},{"unicode":9544,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":373.5,"bottom":486.5,"right":387.5,"top":511.5}},{"unicode":9545,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":388.5,"bottom":486.5,"right":402.5,"top":511.5}},{"unicode":9546,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":403.5,"bottom":486.5,"right":417.5,"top":511.5}},{"unicode":9547,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":418.5,"bottom":486.5,"right":432.5,"top":511.5}},{"unicode":9548,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":0.10076923076923083,"right":0.72307692307692306,"top":0.63923076923076927},"atlasBounds":{"left":53.5,"bottom":47.5,"right":64.5,"top":54.5}},{"unicode":9549,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692299,"bottom":0.062307692307692383,"right":0.72307692307692306,"top":0.6776923076923077},"atlasBounds":{"left":250.5,"bottom":56.5,"right":261.5,"top":64.5}},{"unicode":9550,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230823,"bottom":-0.36676923076923063,"right":0.56923076923076932,"top":1.0947692307692309},"atlasBounds":{"left":80.5,"bottom":418.5,"right":87.5,"top":437.5}},{"unicode":9551,"advance":0.59999999999999998,"planeBounds":{"left":-0.0076923076923076155,"bottom":-0.36676923076923074,"right":0.60769230769230775,"top":1.0947692307692309},"atlasBounds":{"left":88.5,"bottom":418.5,"right":96.5,"top":437.5}},{"unicode":9552,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":0.02384615384615392,"right":0.83846153846153848,"top":0.71615384615384625},"atlasBounds":{"left":497.5,"bottom":77.5,"right":511.5,"top":86.5}},{"unicode":9553,"advance":0.59999999999999998,"planeBounds":{"left":-0.04615384615384608,"bottom":-0.60153846153846147,"right":0.64615384615384619,"top":1.3215384615384616},"atlasBounds":{"left":433.5,"bottom":486.5,"right":442.5,"top":511.5}},{"unicode":9554,"advance":0.59999999999999998,"planeBounds":{"left":0.050384615384615458,"bottom":-0.59384615384615369,"right":0.81961538461538463,"top":0.71384615384615402},"atlasBounds":{"left":433.5,"bottom":343.5,"right":443.5,"top":360.5}},{"unicode":9555,"advance":0.59999999999999998,"planeBounds":{"left":-0.07653846153846143,"bottom":-0.60538461538461541,"right":0.84653846153846168,"top":0.62538461538461554},"atlasBounds":{"left":470.5,"bottom":307.5,"right":482.5,"top":323.5}},{"unicode":9556,"advance":0.59999999999999998,"planeBounds":{"left":-0.07653846153846143,"bottom":-0.59384615384615369,"right":0.84653846153846168,"top":0.71384615384615402},"atlasBounds":{"left":420.5,"bottom":343.5,"right":432.5,"top":360.5}},{"unicode":9557,"advance":0.59999999999999998,"planeBounds":{"left":-0.21961538461538455,"bottom":-0.59384615384615369,"right":0.54961538461538462,"top":0.71384615384615402},"atlasBounds":{"left":409.5,"bottom":343.5,"right":419.5,"top":360.5}},{"unicode":9558,"advance":0.59999999999999998,"planeBounds":{"left":-0.24653846153846143,"bottom":-0.60538461538461541,"right":0.67653846153846164,"top":0.62538461538461554},"atlasBounds":{"left":444.5,"bottom":307.5,"right":456.5,"top":323.5}},{"unicode":9559,"advance":0.59999999999999998,"planeBounds":{"left":-0.24653846153846143,"bottom":-0.59384615384615369,"right":0.67653846153846164,"top":0.71384615384615402},"atlasBounds":{"left":359.5,"bottom":343.5,"right":371.5,"top":360.5}},{"unicode":9560,"advance":0.59999999999999998,"planeBounds":{"left":0.050384615384615458,"bottom":0.016153846153846255,"right":0.81961538461538463,"top":1.3238461538461541},"atlasBounds":{"left":348.5,"bottom":343.5,"right":358.5,"top":360.5}},{"unicode":9561,"advance":0.59999999999999998,"planeBounds":{"left":-0.07653846153846143,"bottom":0.10461538461538476,"right":0.84653846153846168,"top":1.3353846153846158},"atlasBounds":{"left":394.5,"bottom":307.5,"right":406.5,"top":323.5}},{"unicode":9562,"advance":0.59999999999999998,"planeBounds":{"left":-0.07653846153846143,"bottom":0.016153846153846255,"right":0.84653846153846168,"top":1.3238461538461541},"atlasBounds":{"left":335.5,"bottom":343.5,"right":347.5,"top":360.5}},{"unicode":9563,"advance":0.59999999999999998,"planeBounds":{"left":-0.21961538461538455,"bottom":0.016153846153846255,"right":0.54961538461538462,"top":1.3238461538461541},"atlasBounds":{"left":324.5,"bottom":343.5,"right":334.5,"top":360.5}},{"unicode":9564,"advance":0.59999999999999998,"planeBounds":{"left":-0.24653846153846143,"bottom":0.10461538461538476,"right":0.67653846153846164,"top":1.3353846153846158},"atlasBounds":{"left":368.5,"bottom":307.5,"right":380.5,"top":323.5}},{"unicode":9565,"advance":0.59999999999999998,"planeBounds":{"left":-0.24653846153846143,"bottom":0.016153846153846255,"right":0.67653846153846164,"top":1.3238461538461541},"atlasBounds":{"left":311.5,"bottom":343.5,"right":323.5,"top":360.5}},{"unicode":9566,"advance":0.59999999999999998,"planeBounds":{"left":0.050384615384615458,"bottom":-0.60153846153846147,"right":0.81961538461538463,"top":1.3215384615384616},"atlasBounds":{"left":443.5,"bottom":486.5,"right":453.5,"top":511.5}},{"unicode":9567,"advance":0.59999999999999998,"planeBounds":{"left":-0.07653846153846143,"bottom":-0.60153846153846147,"right":0.84653846153846168,"top":1.3215384615384616},"atlasBounds":{"left":454.5,"bottom":486.5,"right":466.5,"top":511.5}},{"unicode":9568,"advance":0.59999999999999998,"planeBounds":{"left":-0.07653846153846143,"bottom":-0.60153846153846147,"right":0.84653846153846168,"top":1.3215384615384616},"atlasBounds":{"left":467.5,"bottom":486.5,"right":479.5,"top":511.5}},{"unicode":9569,"advance":0.59999999999999998,"planeBounds":{"left":-0.21961538461538455,"bottom":-0.60153846153846147,"right":0.54961538461538462,"top":1.3215384615384616},"atlasBounds":{"left":480.5,"bottom":486.5,"right":490.5,"top":511.5}},{"unicode":9570,"advance":0.59999999999999998,"planeBounds":{"left":-0.24653846153846143,"bottom":-0.60153846153846147,"right":0.67653846153846164,"top":1.3215384615384616},"atlasBounds":{"left":491.5,"bottom":486.5,"right":503.5,"top":511.5}},{"unicode":9571,"advance":0.59999999999999998,"planeBounds":{"left":-0.24653846153846143,"bottom":-0.60153846153846147,"right":0.67653846153846164,"top":1.3215384615384616},"atlasBounds":{"left":0.5,"bottom":460.5,"right":12.5,"top":485.5}},{"unicode":9572,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.59384615384615369,"right":0.83846153846153848,"top":0.71384615384615402},"atlasBounds":{"left":169.5,"bottom":306.5,"right":183.5,"top":323.5}},{"unicode":9573,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60538461538461541,"right":0.83846153846153848,"top":0.62538461538461554},"atlasBounds":{"left":46.5,"bottom":238.5,"right":60.5,"top":254.5}},{"unicode":9574,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.59384615384615369,"right":0.83846153846153848,"top":0.71384615384615402},"atlasBounds":{"left":328.5,"bottom":306.5,"right":342.5,"top":323.5}},{"unicode":9575,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":0.016153846153846255,"right":0.83846153846153848,"top":1.3238461538461541},"atlasBounds":{"left":313.5,"bottom":306.5,"right":327.5,"top":323.5}},{"unicode":9576,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":0.10461538461538476,"right":0.83846153846153848,"top":1.3353846153846158},"atlasBounds":{"left":241.5,"bottom":272.5,"right":255.5,"top":288.5}},{"unicode":9577,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":0.016153846153846255,"right":0.83846153846153848,"top":1.3238461538461541},"atlasBounds":{"left":273.5,"bottom":306.5,"right":287.5,"top":323.5}},{"unicode":9578,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":13.5,"bottom":460.5,"right":27.5,"top":485.5}},{"unicode":9579,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":28.5,"bottom":460.5,"right":42.5,"top":485.5}},{"unicode":9580,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.60153846153846147,"right":0.83846153846153848,"top":1.3215384615384616},"atlasBounds":{"left":43.5,"bottom":460.5,"right":57.5,"top":485.5}},{"unicode":9581,"advance":0.59999999999999998,"planeBounds":{"left":0.050384615384615458,"bottom":-0.60538461538461541,"right":0.81961538461538463,"top":0.62538461538461554},"atlasBounds":{"left":99.5,"bottom":255.5,"right":109.5,"top":271.5}},{"unicode":9582,"advance":0.59999999999999998,"planeBounds":{"left":-0.21961538461538455,"bottom":-0.60538461538461541,"right":0.54961538461538462,"top":0.62538461538461554},"atlasBounds":{"left":121.5,"bottom":255.5,"right":131.5,"top":271.5}},{"unicode":9583,"advance":0.59999999999999998,"planeBounds":{"left":-0.21961538461538455,"bottom":0.10461538461538476,"right":0.54961538461538462,"top":1.3353846153846158},"atlasBounds":{"left":268.5,"bottom":255.5,"right":278.5,"top":271.5}},{"unicode":9584,"advance":0.59999999999999998,"planeBounds":{"left":0.050384615384615458,"bottom":0.10461538461538476,"right":0.81961538461538463,"top":1.3353846153846158},"atlasBounds":{"left":292.5,"bottom":255.5,"right":302.5,"top":271.5}},{"unicode":9585,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.37076923076923068,"right":0.83846153846153848,"top":1.0907692307692309},"atlasBounds":{"left":421.5,"bottom":440.5,"right":435.5,"top":459.5}},{"unicode":9586,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.37076923076923068,"right":0.83846153846153848,"top":1.0907692307692309},"atlasBounds":{"left":406.5,"bottom":440.5,"right":420.5,"top":459.5}},{"unicode":9587,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.37076923076923068,"right":0.83846153846153848,"top":1.0907692307692309},"atlasBounds":{"left":391.5,"bottom":440.5,"right":405.5,"top":459.5}},{"unicode":9588,"advance":0.59999999999999998,"planeBounds":{"left":-0.21961538461538455,"bottom":0.10076923076923083,"right":0.54961538461538462,"top":0.63923076923076927},"atlasBounds":{"left":151.5,"bottom":47.5,"right":161.5,"top":54.5}},{"unicode":9589,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230823,"bottom":0.10461538461538476,"right":0.56923076923076932,"top":1.3353846153846158},"atlasBounds":{"left":342.5,"bottom":255.5,"right":349.5,"top":271.5}},{"unicode":9590,"advance":0.59999999999999998,"planeBounds":{"left":0.050384615384615458,"bottom":0.10076923076923083,"right":0.81961538461538463,"top":0.63923076923076927},"atlasBounds":{"left":494.5,"bottom":57.5,"right":504.5,"top":64.5}},{"unicode":9591,"advance":0.59999999999999998,"planeBounds":{"left":0.030769230769230823,"bottom":-0.60538461538461541,"right":0.56923076923076932,"top":0.62538461538461554},"atlasBounds":{"left":486.5,"bottom":255.5,"right":493.5,"top":271.5}},{"unicode":9592,"advance":0.59999999999999998,"planeBounds":{"left":-0.21961538461538455,"bottom":0.062307692307692383,"right":0.54961538461538462,"top":0.6776923076923077},"atlasBounds":{"left":213.5,"bottom":56.5,"right":223.5,"top":64.5}},{"unicode":9593,"advance":0.59999999999999998,"planeBounds":{"left":-0.0076923076923076155,"bottom":0.10461538461538476,"right":0.60769230769230775,"top":1.3353846153846158},"atlasBounds":{"left":25.5,"bottom":238.5,"right":33.5,"top":254.5}},{"unicode":9594,"advance":0.59999999999999998,"planeBounds":{"left":0.050384615384615458,"bottom":0.062307692307692383,"right":0.81961538461538463,"top":0.6776923076923077},"atlasBounds":{"left":501.5,"bottom":165.5,"right":511.5,"top":173.5}},{"unicode":9595,"advance":0.59999999999999998,"planeBounds":{"left":-0.0076923076923076155,"bottom":-0.60538461538461541,"right":0.60769230769230775,"top":0.62538461538461554},"atlasBounds":{"left":61.5,"bottom":238.5,"right":69.5,"top":254.5}},{"unicode":9596,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":0.062307692307692383,"right":0.83846153846153848,"top":0.6776923076923077},"atlasBounds":{"left":130.5,"bottom":56.5,"right":144.5,"top":64.5}},{"unicode":9597,"advance":0.59999999999999998,"planeBounds":{"left":-0.0076923076923076155,"bottom":-0.60153846153846147,"right":0.60769230769230775,"top":1.3215384615384616},"atlasBounds":{"left":58.5,"bottom":460.5,"right":66.5,"top":485.5}},{"unicode":9598,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":0.062307692307692383,"right":0.83846153846153848,"top":0.6776923076923077},"atlasBounds":{"left":47.5,"bottom":56.5,"right":61.5,"top":64.5}},{"unicode":9599,"advance":0.59999999999999998,"planeBounds":{"left":-0.0076923076923076155,"bottom":-0.60153846153846147,"right":0.60769230769230775,"top":1.3215384615384616},"atlasBounds":{"left":67.5,"bottom":460.5,"right":75.5,"top":485.5}},{"unicode":9600,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":0.15153846153846168,"right":0.80000000000000004,"top":1.2284615384615385},"atlasBounds":{"left":364.5,"bottom":143.5,"right":377.5,"top":157.5}},{"unicode":9601,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.52519230769230763,"right":0.80000000000000004,"top":0.090192307692307738},"atlasBounds":{"left":288.5,"bottom":56.5,"right":301.5,"top":64.5}},{"unicode":9602,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.5196153846153847,"right":0.80000000000000004,"top":0.24961538461538454},"atlasBounds":{"left":220.5,"bottom":76.5,"right":233.5,"top":86.5}},{"unicode":9603,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.51403846153846156,"right":0.80000000000000004,"top":0.40903846153846157},"atlasBounds":{"left":389.5,"bottom":101.5,"right":402.5,"top":113.5}},{"unicode":9604,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.5084615384615383,"right":0.80000000000000004,"top":0.56846153846153846},"atlasBounds":{"left":378.5,"bottom":143.5,"right":391.5,"top":157.5}},{"unicode":9605,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.50288461538461526,"right":0.80000000000000004,"top":0.72788461538461557},"atlasBounds":{"left":154.5,"bottom":272.5,"right":167.5,"top":288.5}},{"unicode":9606,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.49730769230769217,"right":0.80000000000000004,"top":0.88730769230769246},"atlasBounds":{"left":135.5,"bottom":380.5,"right":148.5,"top":398.5}},{"unicode":9607,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.53019230769230752,"right":0.80000000000000004,"top":1.0851923076923078},"atlasBounds":{"left":17.5,"bottom":438.5,"right":30.5,"top":459.5}},{"unicode":9608,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.52461538461538448,"right":0.80000000000000004,"top":1.2446153846153847},"atlasBounds":{"left":206.5,"bottom":462.5,"right":219.5,"top":485.5}},{"unicode":9609,"advance":0.59999999999999998,"planeBounds":{"left":-0.19903846153846144,"bottom":-0.52461538461538448,"right":0.72403846153846163,"top":1.2446153846153847},"atlasBounds":{"left":220.5,"bottom":462.5,"right":232.5,"top":485.5}},{"unicode":9610,"advance":0.59999999999999998,"planeBounds":{"left":-0.19807692307692298,"bottom":-0.52461538461538448,"right":0.64807692307692311,"top":1.2446153846153847},"atlasBounds":{"left":233.5,"bottom":462.5,"right":244.5,"top":485.5}},{"unicode":9611,"advance":0.59999999999999998,"planeBounds":{"left":-0.1971153846153845,"bottom":-0.52461538461538448,"right":0.57211538461538469,"top":1.2446153846153847},"atlasBounds":{"left":245.5,"bottom":462.5,"right":255.5,"top":485.5}},{"unicode":9612,"advance":0.59999999999999998,"planeBounds":{"left":-0.19615384615384615,"bottom":-0.52461538461538448,"right":0.49615384615384611,"top":1.2446153846153847},"atlasBounds":{"left":256.5,"bottom":462.5,"right":265.5,"top":485.5}},{"unicode":9613,"advance":0.59999999999999998,"planeBounds":{"left":-0.20769230769230765,"bottom":-0.52461538461538448,"right":0.40769230769230774,"top":1.2446153846153847},"atlasBounds":{"left":266.5,"bottom":462.5,"right":274.5,"top":485.5}},{"unicode":9614,"advance":0.59999999999999998,"planeBounds":{"left":-0.19423076923076921,"bottom":-0.52461538461538448,"right":0.34423076923076928,"top":1.2446153846153847},"atlasBounds":{"left":275.5,"bottom":462.5,"right":282.5,"top":485.5}},{"unicode":9615,"advance":0.59999999999999998,"planeBounds":{"left":-0.19326923076923078,"bottom":-0.52461538461538448,"right":0.26826923076923076,"top":1.2446153846153847},"atlasBounds":{"left":283.5,"bottom":462.5,"right":289.5,"top":485.5}},{"unicode":9616,"advance":0.59999999999999998,"planeBounds":{"left":0.10384615384615392,"bottom":-0.52461538461538448,"right":0.79615384615384621,"top":1.2446153846153847},"atlasBounds":{"left":290.5,"bottom":462.5,"right":299.5,"top":485.5}},{"unicode":9617,"advance":0.59999999999999998,"planeBounds":{"left":-0.1699999999999999,"bottom":-0.45615384615384608,"right":0.83000000000000007,"top":1.2361538461538462},"atlasBounds":{"left":432.5,"bottom":463.5,"right":445.5,"top":485.5}},{"unicode":9618,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.52461538461538448,"right":0.80000000000000004,"top":1.2446153846153847},"atlasBounds":{"left":300.5,"bottom":462.5,"right":313.5,"top":485.5}},{"unicode":9619,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.52461538461538448,"right":0.80000000000000004,"top":1.2446153846153847},"atlasBounds":{"left":314.5,"bottom":462.5,"right":327.5,"top":485.5}},{"unicode":9620,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":0.62980769230769229,"right":0.80000000000000004,"top":1.2451923076923077},"atlasBounds":{"left":191.5,"bottom":56.5,"right":204.5,"top":64.5}},{"unicode":9621,"advance":0.59999999999999998,"planeBounds":{"left":0.33173076923076927,"bottom":-0.52461538461538448,"right":0.79326923076923084,"top":1.2446153846153847},"atlasBounds":{"left":328.5,"bottom":462.5,"right":334.5,"top":485.5}},{"unicode":9622,"advance":0.59999999999999998,"planeBounds":{"left":-0.19615384615384615,"bottom":-0.5084615384615383,"right":0.49615384615384611,"top":0.56846153846153846},"atlasBounds":{"left":385.5,"bottom":159.5,"right":394.5,"top":173.5}},{"unicode":9623,"advance":0.59999999999999998,"planeBounds":{"left":0.10384615384615392,"bottom":-0.5084615384615383,"right":0.79615384615384621,"top":0.56846153846153846},"atlasBounds":{"left":52.5,"bottom":143.5,"right":61.5,"top":157.5}},{"unicode":9624,"advance":0.59999999999999998,"planeBounds":{"left":-0.19615384615384615,"bottom":0.15153846153846168,"right":0.49615384615384611,"top":1.2284615384615385},"atlasBounds":{"left":436.5,"bottom":143.5,"right":445.5,"top":157.5}},{"unicode":9625,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.52461538461538448,"right":0.80000000000000004,"top":1.2446153846153847},"atlasBounds":{"left":335.5,"bottom":462.5,"right":348.5,"top":485.5}},{"unicode":9626,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.52461538461538448,"right":0.80000000000000004,"top":1.2446153846153847},"atlasBounds":{"left":349.5,"bottom":462.5,"right":362.5,"top":485.5}},{"unicode":9627,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.52461538461538448,"right":0.80000000000000004,"top":1.2446153846153847},"atlasBounds":{"left":363.5,"bottom":462.5,"right":376.5,"top":485.5}},{"unicode":9628,"advance":0.59999999999999998,"planeBounds":{"left":-0.20499999999999988,"bottom":-0.52461538461538448,"right":0.79500000000000004,"top":1.2446153846153847},"atlasBounds":{"left":377.5,"bottom":462.5,"right":390.5,"top":485.5}},{"unicode":9629,"advance":0.59999999999999998,"planeBounds":{"left":0.10384615384615392,"bottom":0.15153846153846168,"right":0.79615384615384621,"top":1.2284615384615385},"atlasBounds":{"left":339.5,"bottom":143.5,"right":348.5,"top":157.5}},{"unicode":9630,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.52461538461538448,"right":0.80000000000000004,"top":1.2446153846153847},"atlasBounds":{"left":391.5,"bottom":462.5,"right":404.5,"top":485.5}},{"unicode":9631,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.52461538461538448,"right":0.80000000000000004,"top":1.2446153846153847},"atlasBounds":{"left":405.5,"bottom":462.5,"right":418.5,"top":485.5}},{"unicode":9632,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.13999999999999993,"right":0.80000000000000004,"top":0.85999999999999999},"atlasBounds":{"left":143.5,"bottom":100.5,"right":156.5,"top":113.5}},{"unicode":9633,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.13999999999999993,"right":0.80000000000000004,"top":0.85999999999999999},"atlasBounds":{"left":131.5,"bottom":129.5,"right":144.5,"top":142.5}},{"unicode":9642,"advance":0.59999999999999998,"planeBounds":{"left":-0.04615384615384608,"bottom":0.013846153846153918,"right":0.64615384615384619,"top":0.70615384615384624},"atlasBounds":{"left":379.5,"bottom":65.5,"right":388.5,"top":74.5}},{"unicode":9643,"advance":0.59999999999999998,"planeBounds":{"left":-0.04615384615384608,"bottom":0.013846153846153918,"right":0.64615384615384619,"top":0.70615384615384624},"atlasBounds":{"left":383.5,"bottom":77.5,"right":392.5,"top":86.5}},{"unicode":9650,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.1349999999999999,"right":0.83846153846153837,"top":0.86499999999999999},"atlasBounds":{"left":51.5,"bottom":114.5,"right":65.5,"top":127.5}},{"unicode":9651,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.1349999999999999,"right":0.83846153846153837,"top":0.86499999999999999},"atlasBounds":{"left":160.5,"bottom":129.5,"right":174.5,"top":142.5}},{"unicode":9652,"advance":0.59999999999999998,"planeBounds":{"left":-0.04615384615384608,"bottom":-0.019615384615384507,"right":0.64615384615384619,"top":0.74961538461538468},"atlasBounds":{"left":269.5,"bottom":76.5,"right":278.5,"top":86.5}},{"unicode":9653,"advance":0.59999999999999998,"planeBounds":{"left":-0.04615384615384608,"bottom":-0.019615384615384507,"right":0.64615384615384619,"top":0.74961538461538468},"atlasBounds":{"left":97.5,"bottom":76.5,"right":106.5,"top":86.5}},{"unicode":9654,"advance":0.59999999999999998,"planeBounds":{"left":-0.1949999999999999,"bottom":-0.17846153846153839,"right":0.80500000000000005,"top":0.89846153846153831},"atlasBounds":{"left":195.5,"bottom":143.5,"right":208.5,"top":157.5}},{"unicode":9655,"advance":0.59999999999999998,"planeBounds":{"left":-0.1949999999999999,"bottom":-0.17846153846153839,"right":0.80500000000000005,"top":0.89846153846153831},"atlasBounds":{"left":181.5,"bottom":143.5,"right":194.5,"top":157.5}},{"unicode":9656,"advance":0.59999999999999998,"planeBounds":{"left":-0.079615384615384505,"bottom":0.013846153846153918,"right":0.68961538461538463,"top":0.70615384615384624},"atlasBounds":{"left":326.5,"bottom":65.5,"right":336.5,"top":74.5}},{"unicode":9657,"advance":0.59999999999999998,"planeBounds":{"left":-0.079615384615384505,"bottom":0.013846153846153918,"right":0.68961538461538463,"top":0.70615384615384624},"atlasBounds":{"left":486.5,"bottom":77.5,"right":496.5,"top":86.5}},{"unicode":9658,"advance":0.59999999999999998,"planeBounds":{"left":-0.1949999999999999,"bottom":0.013846153846153918,"right":0.80500000000000005,"top":0.70615384615384624},"atlasBounds":{"left":410.5,"bottom":65.5,"right":423.5,"top":74.5}},{"unicode":9659,"advance":0.59999999999999998,"planeBounds":{"left":-0.1949999999999999,"bottom":0.013846153846153918,"right":0.80500000000000005,"top":0.70615384615384624},"atlasBounds":{"left":365.5,"bottom":65.5,"right":378.5,"top":74.5}},{"unicode":9660,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.14499999999999988,"right":0.83846153846153837,"top":0.85499999999999998},"atlasBounds":{"left":145.5,"bottom":129.5,"right":159.5,"top":142.5}},{"unicode":9661,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.14499999999999988,"right":0.83846153846153837,"top":0.85499999999999998},"atlasBounds":{"left":58.5,"bottom":129.5,"right":72.5,"top":142.5}},{"unicode":9662,"advance":0.59999999999999998,"planeBounds":{"left":-0.04615384615384608,"bottom":-0.029615384615384509,"right":0.64615384615384619,"top":0.73961538461538467},"atlasBounds":{"left":210.5,"bottom":76.5,"right":219.5,"top":86.5}},{"unicode":9663,"advance":0.59999999999999998,"planeBounds":{"left":-0.04615384615384608,"bottom":-0.029615384615384509,"right":0.64615384615384619,"top":0.73961538461538467},"atlasBounds":{"left":139.5,"bottom":76.5,"right":148.5,"top":86.5}},{"unicode":9664,"advance":0.59999999999999998,"planeBounds":{"left":-0.20499999999999988,"bottom":-0.17896153846153839,"right":0.79500000000000004,"top":0.89796153846153837},"atlasBounds":{"left":392.5,"bottom":143.5,"right":405.5,"top":157.5}},{"unicode":9665,"advance":0.59999999999999998,"planeBounds":{"left":-0.20499999999999988,"bottom":-0.17846153846153839,"right":0.79500000000000004,"top":0.89846153846153831},"atlasBounds":{"left":492.5,"bottom":143.5,"right":505.5,"top":157.5}},{"unicode":9666,"advance":0.59999999999999998,"planeBounds":{"left":-0.089615384615384527,"bottom":0.013846153846153918,"right":0.67961538461538462,"top":0.70615384615384624},"atlasBounds":{"left":162.5,"bottom":65.5,"right":172.5,"top":74.5}},{"unicode":9667,"advance":0.59999999999999998,"planeBounds":{"left":-0.089615384615384527,"bottom":0.013846153846153918,"right":0.67961538461538462,"top":0.70615384615384624},"atlasBounds":{"left":461.5,"bottom":77.5,"right":471.5,"top":86.5}},{"unicode":9668,"advance":0.59999999999999998,"planeBounds":{"left":-0.20499999999999988,"bottom":0.013846153846153918,"right":0.79500000000000004,"top":0.70615384615384624},"atlasBounds":{"left":303.5,"bottom":65.5,"right":316.5,"top":74.5}},{"unicode":9669,"advance":0.59999999999999998,"planeBounds":{"left":-0.20499999999999988,"bottom":0.013846153846153918,"right":0.79500000000000004,"top":0.70615384615384624},"atlasBounds":{"left":424.5,"bottom":65.5,"right":437.5,"top":74.5}},{"unicode":9670,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.17796153846153839,"right":0.83846153846153837,"top":0.89896153846153837},"atlasBounds":{"left":166.5,"bottom":143.5,"right":180.5,"top":157.5}},{"unicode":9671,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.17796153846153839,"right":0.83846153846153837,"top":0.89896153846153837},"atlasBounds":{"left":279.5,"bottom":143.5,"right":293.5,"top":157.5}},{"unicode":9674,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":-0.21692307692307683,"right":0.76153846153846172,"top":0.93692307692307697},"atlasBounds":{"left":203.5,"bottom":174.5,"right":215.5,"top":189.5}},{"unicode":9676,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.17846153846153839,"right":0.83846153846153837,"top":0.89846153846153831},"atlasBounds":{"left":294.5,"bottom":143.5,"right":308.5,"top":157.5}},{"unicode":9678,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.17846153846153839,"right":0.83846153846153837,"top":0.89846153846153831},"atlasBounds":{"left":349.5,"bottom":143.5,"right":363.5,"top":157.5}},{"unicode":9679,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.17846153846153839,"right":0.83846153846153837,"top":0.89846153846153831},"atlasBounds":{"left":395.5,"bottom":159.5,"right":409.5,"top":173.5}},{"unicode":9684,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.17846153846153839,"right":0.83846153846153837,"top":0.89846153846153831},"atlasBounds":{"left":222.5,"bottom":143.5,"right":236.5,"top":157.5}},{"unicode":9685,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.17846153846153839,"right":0.83846153846153837,"top":0.89846153846153831},"atlasBounds":{"left":406.5,"bottom":143.5,"right":420.5,"top":157.5}},{"unicode":9702,"advance":0.59999999999999998,"planeBounds":{"left":-0.046153846153846052,"bottom":0.018846153846153946,"right":0.64615384615384619,"top":0.71115384615384625},"atlasBounds":{"left":373.5,"bottom":77.5,"right":382.5,"top":86.5}},{"unicode":9703,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.13999999999999993,"right":0.80000000000000004,"top":0.85999999999999999},"atlasBounds":{"left":0.5,"bottom":100.5,"right":13.5,"top":113.5}},{"unicode":9704,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.13999999999999993,"right":0.80000000000000004,"top":0.85999999999999999},"atlasBounds":{"left":87.5,"bottom":129.5,"right":100.5,"top":142.5}},{"unicode":9705,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.13999999999999993,"right":0.80000000000000004,"top":0.85999999999999999},"atlasBounds":{"left":30.5,"bottom":129.5,"right":43.5,"top":142.5}},{"unicode":9706,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.13999999999999993,"right":0.80000000000000004,"top":0.85999999999999999},"atlasBounds":{"left":482.5,"bottom":114.5,"right":495.5,"top":127.5}},{"unicode":9707,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.13999999999999993,"right":0.80000000000000004,"top":0.85999999999999999},"atlasBounds":{"left":101.5,"bottom":129.5,"right":114.5,"top":142.5}},{"unicode":9711,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.17846153846153839,"right":0.83846153846153837,"top":0.89846153846153831},"atlasBounds":{"left":370.5,"bottom":159.5,"right":384.5,"top":173.5}},{"unicode":9837,"advance":0.59999999999999998,"planeBounds":{"left":-0.084615384615384565,"bottom":-0.21692307692307686,"right":0.68461538461538463,"top":0.93692307692307697},"atlasBounds":{"left":110.5,"bottom":239.5,"right":120.5,"top":254.5}},{"unicode":9839,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":225.5,"bottom":222.5,"right":237.5,"top":237.5}},{"unicode":9888,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.20692307692307685,"right":0.83846153846153837,"top":0.94692307692307698},"atlasBounds":{"left":63.5,"bottom":206.5,"right":77.5,"top":221.5}},{"unicode":9889,"advance":0.59999999999999998,"planeBounds":{"left":-0.1999999999999999,"bottom":-0.32730769230769213,"right":0.80000000000000004,"top":1.0573076923076925},"atlasBounds":{"left":363.5,"bottom":419.5,"right":376.5,"top":437.5}},{"unicode":10003,"advance":0.59999999999999998,"planeBounds":{"left":-0.16103846153846146,"bottom":-0.093038461538461431,"right":0.76203846153846166,"top":0.83003846153846172},"atlasBounds":{"left":167.5,"bottom":87.5,"right":179.5,"top":99.5}},{"unicode":10005,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.12753846153846143,"right":0.76153846153846172,"top":0.79553846153846164},"atlasBounds":{"left":180.5,"bottom":87.5,"right":192.5,"top":99.5}},{"unicode":10007,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.10153846153846144,"right":0.76153846153846172,"top":0.82153846153846166},"atlasBounds":{"left":193.5,"bottom":87.5,"right":205.5,"top":99.5}},{"unicode":10038,"advance":0.59999999999999998,"planeBounds":{"left":-0.16203846153846144,"bottom":-0.13999999999999993,"right":0.76103846153846166,"top":0.85999999999999999},"atlasBounds":{"left":27.5,"bottom":100.5,"right":39.5,"top":113.5}},{"unicode":10094,"advance":0.59999999999999998,"planeBounds":{"left":-0.0446538461538461,"bottom":-0.2119230769230768,"right":0.64765384615384625,"top":0.94192307692307697},"atlasBounds":{"left":502.5,"bottom":222.5,"right":511.5,"top":237.5}},{"unicode":10095,"advance":0.59999999999999998,"planeBounds":{"left":-0.047653846153846095,"bottom":-0.2119230769230768,"right":0.64465384615384624,"top":0.94192307692307697},"atlasBounds":{"left":379.5,"bottom":206.5,"right":388.5,"top":221.5}},{"unicode":10096,"advance":0.59999999999999998,"planeBounds":{"left":-0.12057692307692298,"bottom":-0.2119230769230768,"right":0.72557692307692312,"top":0.94192307692307697},"atlasBounds":{"left":0.5,"bottom":190.5,"right":11.5,"top":205.5}},{"unicode":10097,"advance":0.59999999999999998,"planeBounds":{"left":-0.12557692307692297,"bottom":-0.2119230769230768,"right":0.72057692307692311,"top":0.94192307692307697},"atlasBounds":{"left":12.5,"bottom":190.5,"right":23.5,"top":205.5}},{"unicode":10140,"advance":0.59999999999999998,"planeBounds":{"left":-0.1949999999999999,"bottom":-0.1699999999999999,"right":0.80500000000000005,"top":0.83000000000000007},"atlasBounds":{"left":91.5,"bottom":100.5,"right":104.5,"top":113.5}},{"unicode":10214,"advance":0.59999999999999998,"planeBounds":{"left":-0.057115384615384547,"bottom":-0.33230769230769225,"right":0.71211538461538471,"top":1.0523076923076924},"atlasBounds":{"left":485.5,"bottom":419.5,"right":495.5,"top":437.5}},{"unicode":10215,"advance":0.59999999999999998,"planeBounds":{"left":-0.11211538461538456,"bottom":-0.33230769230769225,"right":0.65711538461538466,"top":1.0523076923076924},"atlasBounds":{"left":496.5,"bottom":419.5,"right":506.5,"top":437.5}},{"unicode":10216,"advance":0.59999999999999998,"planeBounds":{"left":-0.079615384615384505,"bottom":-0.3423076923076922,"right":0.68961538461538463,"top":1.0423076923076924},"atlasBounds":{"left":0.5,"bottom":399.5,"right":10.5,"top":417.5}},{"unicode":10217,"advance":0.59999999999999998,"planeBounds":{"left":-0.0896153846153845,"bottom":-0.3423076923076922,"right":0.67961538461538462,"top":1.0423076923076924},"atlasBounds":{"left":11.5,"bottom":399.5,"right":21.5,"top":417.5}},{"unicode":10218,"advance":0.59999999999999998,"planeBounds":{"left":-0.096076923076922977,"bottom":-0.33230769230769225,"right":0.75007692307692309,"top":1.0523076923076924},"atlasBounds":{"left":22.5,"bottom":399.5,"right":33.5,"top":417.5}},{"unicode":10219,"advance":0.59999999999999998,"planeBounds":{"left":-0.150076923076923,"bottom":-0.33230769230769225,"right":0.69607692307692315,"top":1.0523076923076924},"atlasBounds":{"left":34.5,"bottom":399.5,"right":45.5,"top":417.5}},{"unicode":10229,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.13153846153846147,"right":0.83846153846153848,"top":0.79153846153846164},"atlasBounds":{"left":453.5,"bottom":101.5,"right":467.5,"top":113.5}},{"unicode":10230,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.13153846153846147,"right":0.83846153846153848,"top":0.79153846153846164},"atlasBounds":{"left":60.5,"bottom":87.5,"right":74.5,"top":99.5}},{"unicode":10231,"advance":0.59999999999999998,"planeBounds":{"left":-0.27692307692307683,"bottom":-0.13153846153846147,"right":0.87692307692307692,"top":0.79153846153846164},"atlasBounds":{"left":125.5,"bottom":87.5,"right":140.5,"top":99.5}},{"unicode":10518,"advance":0.59999999999999998,"planeBounds":{"left":-0.27692307692307683,"bottom":-0.14807692307692299,"right":0.87692307692307692,"top":0.69807692307692315},"atlasBounds":{"left":476.5,"bottom":88.5,"right":491.5,"top":99.5}},{"unicode":10631,"advance":0.59999999999999998,"planeBounds":{"left":-0.06211538461538451,"bottom":-0.33230769230769214,"right":0.7071153846153847,"top":1.0523076923076924},"atlasBounds":{"left":303.5,"bottom":399.5,"right":313.5,"top":417.5}},{"unicode":10632,"advance":0.59999999999999998,"planeBounds":{"left":-0.10711538461538453,"bottom":-0.33230769230769214,"right":0.66211538461538466,"top":1.0523076923076924},"atlasBounds":{"left":314.5,"bottom":399.5,"right":324.5,"top":417.5}},{"unicode":10752,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153839,"bottom":-0.17846153846153839,"right":0.83846153846153837,"top":0.89846153846153831},"atlasBounds":{"left":309.5,"bottom":143.5,"right":323.5,"top":157.5}},{"unicode":10758,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846149,"bottom":-0.2119230769230768,"right":0.76153846153846172,"top":0.94192307692307697},"atlasBounds":{"left":86.5,"bottom":174.5,"right":98.5,"top":189.5}},{"unicode":11096,"advance":0.59999999999999998,"planeBounds":{"left":-0.23846153846153834,"bottom":-0.23346153846153833,"right":0.83846153846153848,"top":0.84346153846153848},"atlasBounds":{"left":324.5,"bottom":143.5,"right":338.5,"top":157.5}},{"unicode":57504,"advance":0.59999999999999998,"planeBounds":{"left":-0.11653846153846147,"bottom":-0.49615384615384595,"right":0.80653846153846165,"top":1.1961538461538461},"atlasBounds":{"left":419.5,"bottom":463.5,"right":431.5,"top":485.5}},{"unicode":57505,"advance":0.59999999999999998,"planeBounds":{"left":-0.12307692307692301,"bottom":-0.41923076923076918,"right":0.72307692307692306,"top":1.1192307692307693},"atlasBounds":{"left":164.5,"bottom":439.5,"right":175.5,"top":459.5}},{"unicode":57506,"advance":0.59999999999999998,"planeBounds":{"left":-0.16153846153846144,"bottom":-0.29480769230769222,"right":0.76153846153846172,"top":1.0898076923076925},"atlasBounds":{"left":385.5,"bottom":399.5,"right":397.5,"top":417.5}},{"unicode":57520,"advance":0.59999999999999998,"planeBounds":{"left":-0.25846153846153835,"bottom":-0.52461538461538448,"right":0.81846153846153846,"top":1.2446153846153847},"atlasBounds":{"left":191.5,"bottom":462.5,"right":205.5,"top":485.5}},{"unicode":57521,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.52461538461538448,"right":0.80000000000000004,"top":1.2446153846153847},"atlasBounds":{"left":177.5,"bottom":462.5,"right":190.5,"top":485.5}},{"unicode":57522,"advance":0.59999999999999998,"planeBounds":{"left":-0.21846153846153835,"bottom":-0.52461538461538448,"right":0.8584615384615385,"top":1.2446153846153847},"atlasBounds":{"left":162.5,"bottom":462.5,"right":176.5,"top":485.5}},{"unicode":57523,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.52461538461538448,"right":0.80000000000000004,"top":1.2446153846153847},"atlasBounds":{"left":148.5,"bottom":462.5,"right":161.5,"top":485.5}},{"unicode":65533,"advance":0.59999999999999998,"planeBounds":{"left":-0.19999999999999993,"bottom":-0.2124230769230768,"right":0.80000000000000004,"top":0.94142307692307692},"atlasBounds":{"left":186.5,"bottom":158.5,"right":199.5,"top":173.5}}],"kerning":[]} ================================================ FILE: core/assets/src/debug/font/charset.txt ================================================ [0x20, 0x7E] [0x00A0, 0x0131] [0x0134, 0x017F] 0x018F 0x0190 0x0192 0x019B 0x01A0 0x01A1 0x01AF 0x01B0 0x01CD 0x01CE 0x01D0 0x01D2 0x01D4 0X01D6 0x01D8 0x01DA 0x01DC 0x01E6 0x01E7 0x01EA 0x01EB 0x01F4 0x01F5 [0x01FC, 0x01FF] [0x0218, 0x021B] 0x0232 0x0233 0x0237 0x0259 0x02B9 0x02BA 0x02BC 0x02C6 0x02C7 0x02C9 [0x02D8, 0x02DD] 0x02F7 [0x0300, 0x0304] [0x0306, 0x030C] 0x030F 0x0312 0x031B 0x0323 [0x0325, 0x0328] [0x0336, 0x0338] 0x0374 0x0375 0x037E [0x0384, 0x038A] 0x38C [0x038E, 0x03A1] [0x03A3, 0x03CF] [0x03D5, 0x03D7] [0x0401, 0x040C] [0x040E, 0x044F] [0x0451, 0x045C] 0x045E 0x045F [0x0490, 0x0493] 0x049A 0x049B 0x04A2 0x04A3 [0x04AE, 0x04B1] 0x04B6 0x04B7 0x04BA 0x04BB 0x04D8 0x04D9 0x04E8 0x04E9 0x0AEA [0x1E80, 0x1E85] 0x1E9E [0x1EA0, 0x1EF9] 0x2001 0x200B 0x2010 0x2013 0x2014 [0x2018, 0x201A] [0x201C, 0x201E] 0x2020 0x2021 0x2022 0x024 0x2026 0x2030 [0x2032, 0x2034] 0x2039 0x203A 0x203E 0x0203F [0x2044, 0x2046] 0x2070 [0x2074, 0x207A] [0x2080, 0x2089] 0x20AB 0x20AC 0x20AE 0x20BD 0x20BF 0x2113 0x2115 0x2116 0x211A 0x2122 0x2124 0x212E 0x2140 0x218A 0x218B [0x2190, 0x2199] 0x219E 0x21A0 0x21A3 [0x21A5, 0x21A7] 0x21AD 0x21C9 [0x21D1, 0x21D4] 0x21E7 0x21E8 [0x2200, 0x2203] 0x2205 0x2208 0x2209 0x220B 0x220C [0x220E, 0x2212] 0x2215 [0x2218, 0x221A] 0x221E 0x2223 0x2225 [0x2227, 0x222B] [0x2236, 0x2239] 0x223C 0x2243 0x2245 [0x2247, 0x2249] 0x224B 0x2254 0x2257 [0x225F, 0x2262] 0x2264 0x2265 [0x226E, 0x2273] 0x227A 0x227C [0x2282, 0x2288] 0x228E 0x2291 [0x2293, 0x2297] 0x2299 0x229B 0x22A2 0x22A4 0x22A5 0x22B8 0x22C6 0x22CE 0x22D0 [0x22EE, 0x22F1] [0x2302, 0x2305] [0x2308, 0x230B] 0x2318 0x2325 0x2368 0x2373 0x2374 [0x239B, 0x23AD] [0x23FB, 0x23FE] [0x2400, 0x2421] 0x2424 [0x2500, 0x25a1] 0x25AA 0x25AB [0x25B2, 0x25C7] 0x25CA 0x25CC 0x25CE 0x25CF 0x25D4 0x25D5 [0x25E6, 0x25EB] 0x25EF 0x266D 0x266F 0x26A0 0x26A1 0x2713 0x2715 0x2717 0x2736 [0x276E, 0x2771] 0x279C [0x27E6, 0x27EB] [0x27F5, 0x27F7] 0x2916 0x2987 0x2988 0x2A00 0x2A06 0x2B58 [0xE0A0, 0xE0A2] [0xE0B0, 0xE0B3] 0xFFFD ================================================ FILE: core/assets/src/debug/font/msdf_font.frag ================================================ #version 450 layout(location=0) in vec2 uv_cord; layout(location=1) in vec4 color; layout(location=2) flat in uint atlas_index; layout(set=0, binding=1) uniform sampler samp; layout(set=0, binding=2) uniform texture2D atlases[1]; layout(constant_id=0) const float px_range = 2.0; layout(location=0) out vec4 out_color; float median(float r, float g, float b) { return max(min(r, g), min(max(r, g), b)); } void main() { vec3 msd = texture(sampler2D(atlases[atlas_index], samp), uv_cord).rgb; float sd = median(msd.r, msd.g, msd.b); float screen_px_distance = px_range * (sd - 0.5); float opacity = clamp(screen_px_distance + 0.5, 0.0, 1.0); out_color = mix(vec4(color.rgb, 0.0), color, opacity); } ================================================ FILE: core/assets/src/debug/font/msdf_font.vert ================================================ #version 450 layout(location=0) in vec2 box_offset; layout(location=1) in vec2 box_size; layout(location=2) in vec2 atlas_offset; layout(location=3) in vec2 atlas_size; layout(location=4) in vec4 color; layout(location=5) in uint atlas_index; layout(location=7) in vec2 vertex_multiplier; layout(push_constant) uniform instance_data { vec2 global_offset; }; layout(set=0, binding=0) uniform framebuffer_data { vec2 framebuffer_size; }; layout(location=0) out vec2 uv_cord; layout(location=1) out vec4 out_color; layout(location=2) flat out uint out_atlas_index; void main() { uv_cord = atlas_offset + (vertex_multiplier * atlas_size); out_color = vec4(color.rgb, 1.0); out_atlas_index = atlas_index; vec2 position = (box_offset + (vertex_multiplier * box_size) + global_offset) * 50; position = ((position / framebuffer_size) - 0.5) * 2.0; gl_Position = vec4(position, 0.0, 1.0); } ================================================ FILE: core/assets/src/emulator/debug/background.frag ================================================ #version 450 layout(input_attachment_index=0, set=0, binding=0) uniform subpassInput rendered; layout(location=0) in vec2 in_pixel_coord; layout(location=0) out vec4 out_color; const float BASE_VALUE[2] = float[](0.2, 0.4); const float OFFSET_VALUE[2] = float[](0.0, -0.1); vec3 generate_bg() { int x = int(round(in_pixel_coord.x)); int y = int(round(in_pixel_coord.y)); float base = BASE_VALUE[((x / 200) + (y / 200)) % 2]; float offset = OFFSET_VALUE[((x / 20) + (y / 20)) % 2]; return vec3(base + offset); } void main() { vec4 in_color = subpassLoad(rendered); float alpha = in_color.a; out_color = vec4(((1.0 - alpha) * generate_bg()) + (alpha * in_color.rgb), 1.0); } ================================================ FILE: core/assets/src/emulator/debug/background.vert ================================================ #version 450 vec2 positions[4] = vec2[]( vec2(-1.0, 1.0), vec2(-1.0, -1.0), vec2(1.0, 1.0), vec2(1.0, -1.0) ); vec2 pixel_coords[4] = vec2[]( vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 0.0), vec2(1.0, 1.0) ); layout(constant_id=0) const float FRAMEBUFFER_WIDTH = 1.0; layout(constant_id=1) const float FRAMEBUFFER_HEIGHT = 1.0; layout(location=0) out vec2 out_pixel_coord; void main() { gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); out_pixel_coord = vec2(FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT) * pixel_coords[gl_VertexIndex]; } ================================================ FILE: core/assets/src/emulator/debug/color.vert ================================================ #version 450 /** * A debug shader passing color data to the fragment shader. */ #include layout(location=0) in vec3 in_position; layout(location=1) in vec4 in_color; layout(location=0) out vec4 out_color; void main() { gl_Position = mc_transform_position(in_position); out_color = in_color; } ================================================ FILE: core/assets/src/emulator/debug/debug.frag ================================================ #version 450 layout(location=0) in vec4 in_color; layout(location=0) out vec4 out_color; void main() { out_color = in_color; } ================================================ FILE: core/assets/src/emulator/debug/null.vert ================================================ #version 450 /** * A debug shader passing 0 to the fragment shader. */ #include layout(location=0) in vec3 in_position; layout(location=0) out vec4 out_color; void main() { gl_Position = mc_transform_position(in_position); out_color = vec4(0.0, 0.0, 0.0, 0.0); } ================================================ FILE: core/assets/src/emulator/debug/position.vert ================================================ #version 450 /** * A debug shader only providing the position of the vertex. */ #include layout(location=0) in vec3 in_position; layout(location=0) out vec4 out_color; void main() { gl_Position = mc_transform_position(in_position); out_color = vec4(0.0, 0.0, 0.0, 1.0); } ================================================ FILE: core/assets/src/emulator/debug/textured.frag ================================================ #version 450 #include layout(location=1) in vec2 in_uv; layout(location=0) out vec4 out_color; layout(constant_id=0) const uint IMAGE_INDEX = 0; void main() { out_color = mc_image(IMAGE_INDEX, in_uv); } ================================================ FILE: core/assets/src/emulator/debug/uv.vert ================================================ #version 450 /** * A debug shader passing uv data to the fragment shader. */ #include layout(location=0) in vec3 in_position; layout(location=1) in vec2 in_uv; layout(location=0) out vec4 out_color; layout(location=1) out vec2 out_uv; void main() { gl_Position = mc_transform_position(in_position); out_color = vec4(in_uv, 0.0, 1.0); out_uv = in_uv; } ================================================ FILE: core/assets/src/emulator/mc_uniforms.glsl ================================================ /** * Defines all inputs to support minecrafts uniforms. */ layout(set=0, binding=1) uniform sampler2D[3] _mc_image; layout(set=0, binding=0, std140) uniform _McStaticUniforms { mat4 projection_matrix; vec4 fog_color; vec3 fog_range_and_game_time; uint fog_shape; vec2 screen_size; } _mc_static_uniforms; /* layout(set=1, binding=0, std140) uniform McSet1Binding0 { mat4 inverse_view_rotation_matrix; mat4 texture_matrix; vec3 light_0_direction; vec3 light_1_direction; vec4 color_modulator; float line_width; } mc_set_1_binding_0;*/ layout(push_constant) uniform _PushConstant { mat4 model_view_matrix; vec3 chunk_offset; } _push_constant; mat4 mc_model_view_matrix() { return _push_constant.model_view_matrix; } mat4 mc_projection_matrix() { return _mc_static_uniforms.projection_matrix; } /* mat4 mc_inverse_view_rotation_matrix() { return mc_set_1_binding_0.inverse_view_rotation_matrix; } mat4 mc_texture_matrix() { return mc_set_1_binding_0.texture_matrix; } vec2 mc_screen_size() { return _mc_static_uniforms.screen_size; } vec4 mc_color_modulator() { return mc_set_1_binding_0.color_modulator; } vec3 mc_light_0_direction() { return mc_set_1_binding_0.light_0_direction; } vec3 mc_light_1_direction() { return mc_set_1_binding_0.light_1_direction; } vec4 mc_fog_color() { return _mc_static_uniforms.fog_color; } float mc_fog_start() { return _mc_static_uniforms.fog_range_and_game_time.x; } float mc_fog_end() { return _mc_static_uniforms.fog_range_and_game_time.y; } uint mc_fog_shape() { return _mc_static_uniforms.fog_shape; } float mc_line_width() { return mc_set_1_binding_0.line_width; } float mc_game_time() { return _mc_static_uniforms.fog_range_and_game_time.z; }*/ vec3 mc_chunk_offset() { return _push_constant.chunk_offset; } vec4 mc_transform_position(vec3 position) { vec4 tmp = mc_projection_matrix() * (mc_model_view_matrix() * vec4(position + mc_chunk_offset(), 1.0)); tmp.z = (tmp.z + tmp.w) / 2.0; tmp.y *= -1.0; return tmp; } vec4 mc_image(uint index, vec2 coord) { return texture(_mc_image[index], coord); } vec4 mc_image_0(vec2 coord) { return texture(_mc_image[0], coord); } vec4 mc_image_1(vec2 coord) { return texture(_mc_image[1], coord); } vec4 mc_image_2(vec2 coord) { return texture(_mc_image[2], coord); } ================================================ FILE: core/assets/src/utils/blit.frag ================================================ #version 450 layout(location=0) in vec2 uv; layout(location=0) out vec4 out_color; layout(set=0,binding=0) uniform sampler2D image; void main() { out_color = texture(image, uv); } ================================================ FILE: core/assets/src/utils/full_screen_quad.vert ================================================ #version 450 layout(location=0) out vec2 uv; vec2 positions[4] = vec2[]( vec2(-1.0, 1.0), vec2(-1.0, -1.0), vec2(1.0, 1.0), vec2(1.0, -1.0) ); vec2 uvs[4] = vec2[]( vec2(0.0, 1.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(1.0, 0.0) ); void main() { gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); uv = uvs[gl_VertexIndex]; } ================================================ FILE: core/natives/.cargo/config.toml ================================================ [env] B4D_RESOURCE_DIR = { value="../assets/build/out/", relative=true } ================================================ FILE: core/natives/.gitignore ================================================ # Gradle local files /build/ # Cargo local files /target/ Cargo.lock ================================================ FILE: core/natives/Cargo.toml ================================================ [package] name = "b4d-core" version = "0.1.0" edition = "2021" [lib] crate-type = ["lib", "cdylib"] [[example]] name = "immediate_cube" crate-type = ["bin"] [features] __internal_doc_test = [] [dependencies] ash = { version="0.37.0", features=["debug", "linked"] } ash-window = "0.10.0" bumpalo = { version="3.9.1", features=["boxed"] } bytemuck = "1.10.0" concurrent-queue = "1.2.2" include_bytes_aligned = "0.1.2" json = "0.12.4" lazy_static = "1.4.0" log = { version="0.4.17", features=["std"] } nalgebra = "0.29.0" ouroboros = "0.15.0" paste = "1.0.6" png = "0.17.5" static_assertions = "1.1.0" shaderc = "0.7.3" vk-profiles-rs = "0.3.0" winit = "0.26.1" xxhash-rust = { version="0.8.2", features=["xxh3", "const_xxh3"] } [build-dependencies] cmake = "0.1.48" [dev-dependencies] env_logger = "0.9.0" rand = "0.8.5" ================================================ FILE: core/natives/build.gradle.kts ================================================ plugins { id("fr.stardustenterprises.rust.wrapper") version "3.2.5" } rust { release.set(true) targets += defaultTarget() } tasks.getByName("build").dependsOn(":core:assets:build") ================================================ FILE: core/natives/build.rs ================================================ extern crate cmake; #[cfg(feature = "docs-rs")] fn main() { } #[cfg(not(feature = "docs-rs"))] fn main() { let dst = cmake::Config::new("libvma").profile("Release").build(); println!("cargo:rustc-link-search=native={}", dst.display()); println!("cargo:rustc-link-lib=static=VulkanMemoryAllocator"); } ================================================ FILE: core/natives/examples/immediate_cube.rs ================================================ extern crate b4d_core; use ash::vk; use bytemuck::{cast_slice, Pod, Zeroable}; use winit::event::{Event, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use b4d_core::prelude::*; use b4d_core::renderer::emulator::debug_pipeline::DebugPipelineMode; use b4d_core::renderer::emulator::mc_shaders::{McUniform, McUniformData, VertexFormat, VertexFormatEntry}; use b4d_core::renderer::emulator::MeshData; use b4d_core::window::WinitWindow; fn main() { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); let event_loop = EventLoop::new(); let window = Box::new(WinitWindow::new("ImmediateCube", 800.0, 600.0, &event_loop)); let b4d = b4d_core::b4d::Blaze4D::new(window, true); b4d.set_debug_mode(Some(DebugPipelineMode::Textured0)); let vertex_format = Vertex::make_b4d_vertex_format(); let mut shader = b4d.create_shader(&vertex_format, McUniform::MODEL_VIEW_MATRIX | McUniform::PROJECTION_MATRIX); let data = MeshData { vertex_data: cast_slice(&CUBE_VERTICES), index_data: cast_slice(&CUBE_INDICES), vertex_stride: std::mem::size_of::() as u32, index_count: CUBE_INDICES.len() as u32, index_type: vk::IndexType::UINT32, primitive_topology: vk::PrimitiveTopology::TRIANGLE_LIST, }; let mut mesh = b4d.create_global_mesh(&data); let mut draw_times = Vec::with_capacity(1000); let mut last_update = std::time::Instant::now(); let mut current_size = Vec2u32::new(800, 600); let start = std::time::Instant::now(); event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Poll; match event { Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => { *control_flow = ControlFlow::Exit }, Event::WindowEvent { event: WindowEvent::Resized(new_size), .. } => { current_size[0] = new_size.width; current_size[1] = new_size.height; } Event::MainEventsCleared => { let now = std::time::Instant::now(); mesh = b4d.create_global_mesh(&data); if let Some(mut recorder) = b4d.try_start_frame(current_size) { recorder.update_uniform(&McUniformData::ProjectionMatrix(make_projection_matrix(current_size, 90f32)), shader); let elapsed = start.elapsed().as_secs_f32(); let rotation = Mat4f32::new_rotation(Vec3f32::new(elapsed / 2.34f32, elapsed / 2.783f32, elapsed / 2.593f32)); for x in -5i32..=5i32 { for y in -5i32..=5i32 { for z in 1i32..=11i32 { let translation = Mat4f32::new_translation(&Vec3f32::new( 0f32 + ((x as f32) / 1f32), 0f32 + ((y as f32) / 1f32), 5f32 + ((z as f32) / 1f32) )); recorder.update_uniform(&McUniformData::ModelViewMatrix(translation * rotation), shader); let id = recorder.upload_immediate(&data); recorder.draw_immediate(id, shader, true); // recorder.draw_global(mesh.clone(), shader, true); } } } drop(recorder); // Stress test the shader stuff b4d.drop_shader(shader); shader = b4d.create_shader(&vertex_format, McUniform::MODEL_VIEW_MATRIX | McUniform::PROJECTION_MATRIX); } draw_times.push(now.elapsed()); if last_update.elapsed().as_secs() >= 2 { let sum = draw_times.iter().fold(0f64, |sum, time| sum + time.as_secs_f64()); let avg = sum / (draw_times.len() as f64); let fps = 1f64 / avg; draw_times.clear(); log::error!("Average frame time over last 2 seconds: {:?} ({:?})", avg, fps); last_update = std::time::Instant::now(); } } _ => { } } }); } const CUBE_VERTICES: [Vertex; 8] = [ Vertex { position: Vec3f32::new(-1f32, -1f32, -1f32), color: Vec4f32::new(0f32, 0f32, 0f32, 1f32), uv: Vec2f32::new(0f32, 0f32), }, Vertex { position: Vec3f32::new(1f32, -1f32, -1f32), color: Vec4f32::new(1f32, 0f32, 0f32, 1f32), uv: Vec2f32::new(1f32, 0f32), }, Vertex { position: Vec3f32::new(-1f32, 1f32, -1f32), color: Vec4f32::new(0f32, 1f32, 0f32, 1f32), uv: Vec2f32::new(0f32, 1f32), }, Vertex { position: Vec3f32::new(1f32, 1f32, -1f32), color: Vec4f32::new(1f32, 1f32, 0f32, 1f32), uv: Vec2f32::new(1f32, 1f32), }, Vertex { position: Vec3f32::new(-1f32, -1f32, 1f32), color: Vec4f32::new(0f32, 0f32, 1f32, 1f32), uv: Vec2f32::new(0f32, 0f32), }, Vertex { position: Vec3f32::new(1f32, -1f32, 1f32), color: Vec4f32::new(1f32, 0f32, 1f32, 1f32), uv: Vec2f32::new(1f32, 0f32), }, Vertex { position: Vec3f32::new(-1f32, 1f32, 1f32), color: Vec4f32::new(0f32, 1f32, 1f32, 1f32), uv: Vec2f32::new(0f32, 1f32), }, Vertex { position: Vec3f32::new(1f32, 1f32, 1f32), color: Vec4f32::new(1f32, 1f32, 1f32, 1f32), uv: Vec2f32::new(1f32, 1f32), }, ]; const CUBE_INDICES: [u32; 36] = [ 4, 6, 7, 7, 5, 4, // Front 3, 2, 0, 0, 1, 3, // Back 6, 2, 3, 3, 7, 6, // Top 0, 4, 5, 5, 1, 0, // Bottom 0, 2, 6, 6, 4, 0, // Left 5, 7, 3, 3, 1, 5, // Right ]; #[derive(Copy, Clone)] struct Vertex { #[allow(unused)] position: Vec3f32, #[allow(unused)] color: Vec4f32, #[allow(unused)] uv: Vec2f32, } impl Vertex { fn make_b4d_vertex_format() -> VertexFormat { VertexFormat { stride: std::mem::size_of::() as u32, position: VertexFormatEntry { offset: 0, format: vk::Format::R32G32B32_SFLOAT }, normal: None, color: Some(VertexFormatEntry { offset: std::mem::size_of::() as u32, format: vk::Format::R32G32B32A32_SFLOAT }), uv0: Some(VertexFormatEntry { offset: std::mem::size_of::() as u32 + std::mem::size_of::() as u32, format: vk::Format::R32G32_SFLOAT }), uv1: None, uv2: None } } } unsafe impl Zeroable for Vertex {} unsafe impl Pod for Vertex {} fn make_projection_matrix(window_size: Vec2u32, fov: f32) -> Mat4f32 { let t = (fov / 2f32).tan(); let a1 = (window_size[1] as f32) / (window_size[0] as f32); let f = 15f32; let n = 0.5f32; Mat4f32::new( a1 / t, 0f32, 0f32, 0f32, 0f32, 1f32 / t, 0f32, 0f32, 0f32, 0f32, f / (f - n), -n * (f - n), 0f32, 0f32, 1f32, 0f32 ) } ================================================ FILE: core/natives/libvma/CMakeLists.txt ================================================ project( b4d_core_vma ) cmake_minimum_required( VERSION 3.13 ) include(FetchContent) FetchContent_Declare( vulkanMemoryAllocator GIT_REPOSITORY https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git GIT_TAG "v3.0.1" ) set(CMAKE_DEBUG_POSTFIX "") set(VMA_STATIC_VULKAN_FUNCTIONS OFF CACHE BOOL "") set(VMA_DYNAMIC_VULKAN_FUNCTIONS ON CACHE BOOL "") FetchContent_MakeAvailable(vulkanMemoryAllocator) install(TARGETS VulkanMemoryAllocator DESTINATION .) ================================================ FILE: core/natives/rustfmt.toml ================================================ max_width = 140 ================================================ FILE: core/natives/src/allocator/mod.rs ================================================ use std::ffi::CString; use std::fmt; use std::ptr::NonNull; use std::sync::Arc; use ash::vk; use crate::prelude::*; mod vma; pub struct Allocator { vma_allocator: vma::Allocator, debug: bool, functions: Arc, } impl Allocator { pub fn new(functions: Arc) -> Result { let vma_allocator = vma::Allocator::new(&functions, vma::AllocatorCreateFlags::empty())?; Ok(Self { vma_allocator, debug: true, functions }) } /// Allocates vulkan memory for some requirements. /// /// Returns the allocation and a [`AllocationBindingInfo`] containing information necessary to /// bind and use the memory. If allocation fails [`None`] is returned. /// /// # Safety /// /// `requirements` must be a valid [`vk::MemoryRequirements`] instance. pub unsafe fn allocate_memory(&self, requirements: &vk::MemoryRequirements, host_access: HostAccess, name: &fmt::Arguments) -> Option<(Allocation, AllocationBindingInfo)> { let create_info = Self::make_default_info(host_access); let mut allocation_info = vma::AllocationInfo::default(); match self.vma_allocator.allocate_memory(requirements, &create_info, Some(&mut allocation_info)) { Ok(allocation) => { if self.debug { self.set_allocation_name(allocation, name); } let binding_info = AllocationBindingInfo::new(&allocation_info); Some((Allocation::new(allocation), binding_info)) } Err(err) => { log::warn!("Failed to allocate vulkan memory for {:?}. {:?}", name, err); None } } } /// Allocates multiple pages of vulkan memory for some requirements. /// /// Returns the allocations and [`AllocationBindingInfo`] containing information necessary to /// bind and use the memory. If allocation fails [`None`] is returned. /// /// # Safety /// /// Every entry in `requirements` must be a valid [`vk::MemoryRequirements`] instance. pub unsafe fn allocate_memory_pages(&self, requirements: &[vk::MemoryRequirements], host_access: HostAccess) -> Option> { let create_info: Box<_> = std::iter::repeat(Self::make_default_info(host_access).build()).take(requirements.len()).collect(); let mut allocation_info = Vec::new(); allocation_info.resize(requirements.len(), vma::AllocationInfo::default()); match self.vma_allocator.allocate_memory_pages(requirements, create_info.as_ref(), Some(&mut allocation_info)) { Ok(allocations) => { debug_assert_eq!(allocations.len(), allocation_info.len()); Some(allocations.into_iter().map(Allocation::new).zip(allocation_info.iter().map(AllocationBindingInfo::new)).collect()) } Err(err) => { log::warn!("Failed to allocate vulkan memory pages {:?}", err); None } } } /// Frees previously allocated memory. /// /// # Safety /// /// The allocation must have been previously allocated from this allocator and not yet freed. pub unsafe fn free_memory(&self, allocation: Allocation) { self.vma_allocator.free_memory(allocation.vma_allocation) } /// Frees multiple previously allocated memory pages. /// /// # Safety /// /// All allocations must have been previously allocated from this allocator and not yet freed. pub unsafe fn free_memory_pages(&self, allocations: &[Allocation]) { let mapped: Box<_> = allocations.iter().map(|a| a.vma_allocation).collect(); self.vma_allocator.free_memory_pages(mapped.as_ref()) } /// Creates a gpu only buffer and binds memory to it. /// /// If creation, allocation or binding fails [`None`] is returned. /// /// # Safety /// /// `create_info` must be a valid [`vk::BufferCreateInfo`] instance. pub unsafe fn create_gpu_buffer(&self, create_info: &vk::BufferCreateInfo, name: &fmt::Arguments) -> Option<(vk::Buffer, Allocation)> { let allocation_create_info = Self::make_default_info(HostAccess::None); match self.vma_allocator.create_buffer(create_info, &allocation_create_info, None) { Ok((buffer, allocation)) => { if self.debug { self.set_allocation_name(allocation, name); } Some((buffer, Allocation::new(allocation))) }, Err(err) => { log::warn!("Failed to create gpu vulkan buffer {:?}. {:?}", name, err); None } } } /// Creates a buffer and binds memory to it. /// /// The allocator may select host visible memory even if it was not requested. In that case a /// pointer to the mapped memory will always be returned. /// /// Returns the buffer, allocation and if host visible memory is selected a pointer to the /// mapped memory. If creation, allocation or binding fails [`None`] is returned. /// /// # Safety /// /// `create_info` must be a valid [`vk::BufferCreateInfo`] instance. pub unsafe fn create_buffer(&self, create_info: &vk::BufferCreateInfo, host_access: HostAccess, name: &fmt::Arguments) -> Option<(vk::Buffer, Allocation, Option>)> { let allocation_create_info = Self::make_default_info(host_access); let mut allocation_info = vma::AllocationInfo::default(); match self.vma_allocator.create_buffer(create_info, &allocation_create_info, Some(&mut allocation_info)) { Ok((buffer, allocation)) => { if self.debug { self.set_allocation_name(allocation, name); } Some((buffer, Allocation::new(allocation), NonNull::new(allocation_info.p_mapped_data as *mut u8))) }, Err(err) => { log::warn!("Failed to create vulkan buffer {:?}. {:?}", name, err); None } } } /// Creates a gpu only image and binds memory to it. /// /// If creation, allocation or binding fails [`None`] is returned. /// /// # Safety /// /// `create_info` must be a valid [`vk::ImageCreateInfo`] instance. pub unsafe fn create_gpu_image(&self, create_info: &vk::ImageCreateInfo, name: &fmt::Arguments) -> Option<(vk::Image, Allocation)> { let allocation_create_info = Self::make_default_info(HostAccess::None); match self.vma_allocator.create_image(create_info, &allocation_create_info, None) { Ok((image, allocation)) => { if self.debug { self.set_allocation_name(allocation, name); } Some((image, Allocation::new(allocation))) }, Err(err) => { log::warn!("Failed to create gpu vulkan image {:?}. {:?}", name, err); None } } } /// Creates a image and binds memory to it. /// /// The allocator may select host visible memory even if it was not requested. In that case a /// pointer to the mapped memory will always be returned. /// /// Returns the image, allocation and if host visible memory is selected a pointer to the /// mapped memory. If creation, allocation or binding fails [`None`] is returned. /// /// # Safety /// /// `create_info` must be a valid [`vk::ImageCreateInfo`] instance. pub unsafe fn create_image(&self, create_info: &vk::ImageCreateInfo, host_access: HostAccess, name: &fmt::Arguments) -> Option<(vk::Image, Allocation, Option>)> { let allocation_create_info = Self::make_default_info(HostAccess::None); let mut allocation_info = vma::AllocationInfo::default(); match self.vma_allocator.create_image(create_info, &allocation_create_info, Some(&mut allocation_info)) { Ok((image, allocation)) => { if self.debug { self.set_allocation_name(allocation, name); } Some((image, Allocation::new(allocation), NonNull::new(allocation_info.p_mapped_data as *mut u8))) }, Err(err) => { log::warn!("Failed to create vulkan image {:?}. {:?}", name, err); None } } } /// Destroys a previously created buffer and allocation /// /// # Safety /// /// `buffer` must be a valid [`vk::Buffer`] handle created on the same device that this /// allocator uses. /// `allocation` must have been previously allocated from this allocator and not yet freed. pub unsafe fn destroy_buffer(&self, buffer: vk::Buffer, allocation: Allocation) { self.vma_allocator.destroy_buffer(buffer, allocation.vma_allocation) } /// Destroys a previously created image and allocation /// /// # Safety /// /// `image` must be a valid [`vk::Image`] handle created on the same device that this /// allocator uses. /// `allocation` must have been previously allocated from this allocator and not yet freed. pub unsafe fn destroy_image(&self, image: vk::Image, allocation: Allocation) { self.vma_allocator.destroy_image(image, allocation.vma_allocation) } unsafe fn set_allocation_name(&self, allocation: vma::Allocation, name: &fmt::Arguments) { if let Some(str) = name.as_str() { self.vma_allocator.set_allocation_name(allocation, CString::new(str).unwrap().as_c_str()) } else { self.vma_allocator.set_allocation_name(allocation, CString::new(name.to_string()).unwrap().as_c_str()) } } fn make_default_info<'a>(host_access: HostAccess) -> vma::AllocationCreateInfoBuilder<'a> { vma::AllocationCreateInfo::builder() .flags(host_access.to_vma_flags()) .usage(vma::MemoryUsage::AUTO) .required_flags(vk::MemoryPropertyFlags::empty()) .preferred_flags(vk::MemoryPropertyFlags::empty()) .memory_type_bits(0) .priority(0.5f32) } } /// Handle of a allocation. This is only a handle and as such any instance must be manually freed. /// /// It is possible copy and clone handles. In that case the using code must ensure only one copy /// is freed. #[derive(Copy, Clone)] pub struct Allocation { vma_allocation: vma::Allocation, } impl Allocation { fn new(vma_allocation: vma::Allocation) -> Self { Self { vma_allocation } } } /// Information needed to bind and access vulkan memory. #[derive(Copy, Clone)] pub struct AllocationBindingInfo { device_memory: vk::DeviceMemory, offset: vk::DeviceSize, size: vk::DeviceSize, mapped_data: Option>, } impl AllocationBindingInfo { fn new(info: &vma::AllocationInfo) -> Self { Self { device_memory: info.device_memory, offset: info.offset, size: info.size, mapped_data: NonNull::new(info.p_mapped_data as *mut u8) } } } /// Describes how the host will access some vulkan memory. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub enum HostAccess { /// Host access is neither required nor preferred None, /// Host access is required. The host will read or write randomly from the memory Random, /// Host access is preferred but not required. The host will read or write randomly from the /// memory. RandomOptional, /// Host access is required. The host will only write sequentially to the memory. SequentialWrite, /// Host access is preferred but not required. The host will only write sequentially to the /// memory. SequentialWriteOptional, } impl HostAccess { fn to_vma_flags(&self) -> vma::AllocationCreateFlags { match self { HostAccess::None => vma::AllocationCreateFlags::empty(), HostAccess::Random => vma::AllocationCreateFlags::HOST_ACCESS_RANDOM | vma::AllocationCreateFlags::CREATE_MAPPED, HostAccess::RandomOptional => vma::AllocationCreateFlags::HOST_ACCESS_RANDOM | vma::AllocationCreateFlags::HOST_ACCESS_ALLOW_TRANSFER_INSTEAD | vma::AllocationCreateFlags::CREATE_MAPPED, HostAccess::SequentialWrite => vma::AllocationCreateFlags::HOST_ACCESS_SEQUENTIAL_WRITE | vma::AllocationCreateFlags::CREATE_MAPPED, HostAccess::SequentialWriteOptional => vma::AllocationCreateFlags::HOST_ACCESS_SEQUENTIAL_WRITE | vma::AllocationCreateFlags::HOST_ACCESS_ALLOW_TRANSFER_INSTEAD | vma::AllocationCreateFlags::CREATE_MAPPED, } } } ================================================ FILE: core/natives/src/allocator/vma.rs ================================================ use std::os::raw::c_char; use std::ffi::{c_void, CStr}; use ash::vk; use crate::prelude::*; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct AllocatorCreateFlags(u32); impl AllocatorCreateFlags { pub const EXTERNALLY_SYNCHRONIZED: AllocatorCreateFlags = AllocatorCreateFlags(0x00000001); pub const DEDICATED_ALLOCATION: AllocatorCreateFlags = AllocatorCreateFlags(0x00000002); pub const KHR_BIND_MEMORY2: AllocatorCreateFlags = AllocatorCreateFlags(0x00000004); pub const EXT_MEMORY_BUDGET: AllocatorCreateFlags = AllocatorCreateFlags(0x00000008); pub const AMD_DEVICE_COHERENT_MEMORY: AllocatorCreateFlags = AllocatorCreateFlags(0x00000010); pub const BUFFER_DEVICE_ADDRESS: AllocatorCreateFlags = AllocatorCreateFlags(0x00000020); pub const EXT_MEMORY_PRIORITY: AllocatorCreateFlags = AllocatorCreateFlags(0x00000040); } ash::vk_bitflags_wrapped!(AllocatorCreateFlags, u32); #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct AllocationCreateFlags(u32); impl AllocationCreateFlags { pub const DEDICATED_MEMORY: AllocationCreateFlags = AllocationCreateFlags(0x00000001); pub const NEVER_ALLOCATE: AllocationCreateFlags = AllocationCreateFlags(0x00000002); pub const CREATE_MAPPED: AllocationCreateFlags = AllocationCreateFlags(0x00000004); pub const UPPER_ADDRESS: AllocationCreateFlags = AllocationCreateFlags(0x00000040); pub const DONT_BIND: AllocationCreateFlags = AllocationCreateFlags(0x00000080); pub const WITHIN_BUDGET: AllocationCreateFlags = AllocationCreateFlags(0x00000100); pub const CAN_ALIAS: AllocationCreateFlags = AllocationCreateFlags(0x00000200); pub const HOST_ACCESS_SEQUENTIAL_WRITE: AllocationCreateFlags = AllocationCreateFlags(0x00000400); pub const HOST_ACCESS_RANDOM: AllocationCreateFlags = AllocationCreateFlags(0x00000800); pub const HOST_ACCESS_ALLOW_TRANSFER_INSTEAD: AllocationCreateFlags = AllocationCreateFlags(0x00001000); pub const STRATEGY_MIN_MEMORY: AllocationCreateFlags = AllocationCreateFlags(0x00010000); pub const STRATEGY_MIN_TIME: AllocationCreateFlags = AllocationCreateFlags(0x00020000); pub const STRATEGY_MIN_OFFSET: AllocationCreateFlags = AllocationCreateFlags(0x00040000); } ash::vk_bitflags_wrapped!(AllocationCreateFlags, u32); #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct MemoryUsage(u32); impl MemoryUsage { pub const UNKNOWN: MemoryUsage = MemoryUsage(0); pub const GPU_LAZILY_ALLOCATED: MemoryUsage = MemoryUsage(6); pub const AUTO: MemoryUsage = MemoryUsage(7); pub const AUTO_PREFER_DEVICE: MemoryUsage = MemoryUsage(8); pub const AUTO_PREFER_HOST: MemoryUsage = MemoryUsage(9); pub const fn from_raw(raw: u32) -> Self { Self(raw) } pub const fn as_raw(self) -> u32 { self.0 } } #[repr(C)] #[derive(Copy, Clone)] pub struct AllocationCreateInfo { pub flags: AllocationCreateFlags, pub usage: MemoryUsage, pub required_flags: vk::MemoryPropertyFlags, pub preferred_flags: vk::MemoryPropertyFlags, pub memory_type_bits: u32, pub pool: *const u8, pub p_user_data: *mut c_void, pub priority: f32, } impl Default for AllocationCreateInfo { fn default() -> Self { Self { flags: AllocationCreateFlags::empty(), usage: MemoryUsage::UNKNOWN, required_flags: vk::MemoryPropertyFlags::empty(), preferred_flags: vk::MemoryPropertyFlags::empty(), memory_type_bits: 0, pool: std::ptr::null(), p_user_data: std::ptr::null_mut(), priority: 0.5 } } } impl AllocationCreateInfo { pub fn builder<'a>() -> AllocationCreateInfoBuilder<'a> { AllocationCreateInfoBuilder { inner: Self::default(), marker: std::marker::PhantomData, } } } #[repr(transparent)] pub struct AllocationCreateInfoBuilder<'a> { inner: AllocationCreateInfo, marker: std::marker::PhantomData<&'a ()>, } impl<'a> std::ops::Deref for AllocationCreateInfoBuilder<'a> { type Target = AllocationCreateInfo; fn deref(&self) -> &Self::Target { &self.inner } } impl<'a> std::ops::DerefMut for AllocationCreateInfoBuilder<'a> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } impl<'a> AllocationCreateInfoBuilder<'a> { #[inline(always)] pub fn flags(mut self, flags: AllocationCreateFlags) -> Self { self.flags = flags; self } #[inline(always)] pub fn usage(mut self, usage: MemoryUsage) -> Self { self.usage = usage; self } #[inline(always)] pub fn required_flags(mut self, required_flags: vk::MemoryPropertyFlags) -> Self { self.required_flags = required_flags; self } #[inline(always)] pub fn preferred_flags(mut self, preferred_flags: vk::MemoryPropertyFlags) -> Self { self.preferred_flags = preferred_flags; self } #[inline(always)] pub fn memory_type_bits(mut self, memory_type_bits: u32) -> Self { self.memory_type_bits = memory_type_bits; self } #[inline(always)] pub fn pool(mut self, pool: *const u8) -> Self { self.pool = pool; self } #[inline(always)] pub fn priority(mut self, priority: f32) -> Self { self.priority = priority; self } /// Calling build will **discard** all the lifetime information. Only call this if /// necessary! Builders implement `Deref` targeting their corresponding vma struct, /// so references to builders can be passed directly to vma functions. #[inline(always)] pub fn build(self) -> AllocationCreateInfo { self.inner } } #[repr(C)] #[derive(Copy, Clone)] pub struct AllocationInfo { pub memory_type: u32, pub device_memory: vk::DeviceMemory, pub offset: vk::DeviceSize, pub size: vk::DeviceSize, pub p_mapped_data: *mut c_void, pub p_user_data: *mut c_void, pub p_name: *const c_char, } impl Default for AllocationInfo { fn default() -> Self { Self { memory_type: 0, device_memory: vk::DeviceMemory::null(), offset: 0, size: 0, p_mapped_data: std::ptr::null_mut(), p_user_data: std::ptr::null_mut(), p_name: std::ptr::null() } } } #[repr(C)] struct VulkanFunctions { vk_get_instance_proc_addr: vk::PFN_vkGetInstanceProcAddr, vk_get_device_proc_addr: vk::PFN_vkGetDeviceProcAddr, _0: *const u8, _1: *const u8, _2: *const u8, _3: *const u8, _4: *const u8, _5: *const u8, _6: *const u8, _7: *const u8, _8: *const u8, _9: *const u8, _10: *const u8, _11: *const u8, _12: *const u8, _13: *const u8, _14: *const u8, _15: *const u8, _16: *const u8, _17: *const u8, _18: *const u8, _19: *const u8, _20: *const u8, _21: *const u8, _22: *const u8, _23: *const u8 } impl VulkanFunctions { fn new_dynamic(entry: &ash::Entry, instance: &ash::Instance) -> Self { Self { vk_get_instance_proc_addr: entry.static_fn().get_instance_proc_addr, vk_get_device_proc_addr: instance.fp_v1_0().get_device_proc_addr, _0: std::ptr::null(), _1: std::ptr::null(), _2: std::ptr::null(), _3: std::ptr::null(), _4: std::ptr::null(), _5: std::ptr::null(), _6: std::ptr::null(), _7: std::ptr::null(), _8: std::ptr::null(), _9: std::ptr::null(), _10: std::ptr::null(), _11: std::ptr::null(), _12: std::ptr::null(), _13: std::ptr::null(), _14: std::ptr::null(), _15: std::ptr::null(), _16: std::ptr::null(), _17: std::ptr::null(), _18: std::ptr::null(), _19: std::ptr::null(), _20: std::ptr::null(), _21: std::ptr::null(), _22: std::ptr::null(), _23: std::ptr::null() } } } #[repr(C)] struct AllocatorCreateInfo { flags: AllocatorCreateFlags, physical_device: vk::PhysicalDevice, device: vk::Device, preferred_large_heap_block_size: vk::DeviceSize, p_allocation_callbacks: *const vk::AllocationCallbacks, p_device_memory_callbacks: *const u8, p_heap_size_limit: *const vk::DeviceSize, p_vulkan_functions: *const VulkanFunctions, instance: vk::Instance, vulkan_api_version: u32, p_type_external_memory_handle_types: *const u8, } #[repr(transparent)] #[derive(Copy, Clone)] struct AllocatorHandle(*const u8); pub struct Allocator { handle: AllocatorHandle, } impl Allocator { pub fn new(device: &DeviceFunctions, create_flags: AllocatorCreateFlags) -> Result { let functions = VulkanFunctions::new_dynamic(device.instance.get_entry(), device.instance.vk()); let info = AllocatorCreateInfo { flags: create_flags, physical_device: device.physical_device, device: device.vk.handle(), preferred_large_heap_block_size: 0, p_allocation_callbacks: std::ptr::null(), p_device_memory_callbacks: std::ptr::null(), p_heap_size_limit: std::ptr::null(), p_vulkan_functions: &functions, instance: device.instance.vk().handle(), vulkan_api_version: device.instance.get_version().get_raw(), p_type_external_memory_handle_types: std::ptr::null() }; let mut handle = AllocatorHandle(std::ptr::null()); let result = unsafe { sys::vmaCreateAllocator(&info, &mut handle) }; if result == vk::Result::SUCCESS { Ok(Self { handle }) } else { Err(result) } } pub unsafe fn allocate_memory(&self, memory_requirements: &vk::MemoryRequirements, create_info: &AllocationCreateInfo, allocation_info: Option<&mut AllocationInfo>) -> Result { let mut handle = Allocation::null(); let allocation_info = allocation_info.map(|i| i as *mut AllocationInfo).unwrap_or(std::ptr::null_mut()); let result = sys::vmaAllocateMemory(self.handle, memory_requirements, create_info, &mut handle, allocation_info); if result == vk::Result::SUCCESS { Ok(handle) } else { Err(result) } } pub unsafe fn allocate_memory_pages(&self, memory_requirements: &[vk::MemoryRequirements], create_info: &[AllocationCreateInfo], allocation_info: Option<&mut [AllocationInfo]>) -> Result, vk::Result> { let count = memory_requirements.len(); assert_eq!(create_info.len(), count); if let Some(info) = &allocation_info { assert_eq!(info.len(), count); } let mut handles = Vec::new(); handles.resize(count, Allocation::null()); let allocation_info = allocation_info.map(|i| i.as_mut_ptr()).unwrap_or(std::ptr::null_mut()); let result = sys::vmaAllocateMemoryPages(self.handle, memory_requirements.as_ptr(), create_info.as_ptr(), count, handles.as_mut_ptr(), allocation_info); if result == vk::Result::SUCCESS { Ok(handles) } else { Err(result) } } pub unsafe fn free_memory(&self, allocation: Allocation) { sys::vmaFreeMemory(self.handle, allocation) } pub unsafe fn free_memory_pages(&self, allocations: &[Allocation]) { sys::vmaFreeMemoryPages(self.handle, allocations.len(), allocations.as_ptr()) } pub unsafe fn get_allocation_info(&self, allocation: Allocation, info: &mut AllocationInfo) { sys::vmaGetAllocationInfo(self.handle, allocation, info) } pub unsafe fn set_allocation_name(&self, allocation: Allocation, name: &CStr) { sys::vmaSetAllocationName(self.handle, allocation, name.as_ptr()) } pub unsafe fn create_buffer(&self, buffer_create_info: &vk::BufferCreateInfo, allocation_create_info: &AllocationCreateInfo, allocation_info: Option<&mut AllocationInfo>) -> Result<(vk::Buffer, Allocation), vk::Result> { let mut buffer_handle = vk::Buffer::null(); let mut allocation_handle = Allocation::null(); let allocation_info = allocation_info.map(|i| i as *mut AllocationInfo).unwrap_or(std::ptr::null_mut()); let result = sys::vmaCreateBuffer(self.handle, buffer_create_info, allocation_create_info, &mut buffer_handle, &mut allocation_handle, allocation_info); if result == vk::Result::SUCCESS { Ok((buffer_handle, allocation_handle)) } else { Err(result) } } pub unsafe fn destroy_buffer(&self, buffer: vk::Buffer, allocation: Allocation) { sys::vmaDestroyBuffer(self.handle, buffer, allocation) } pub unsafe fn create_image(&self, image_create_info: &vk::ImageCreateInfo, allocation_create_info: &AllocationCreateInfo, allocation_info: Option<&mut AllocationInfo>) -> Result<(vk::Image, Allocation), vk::Result> { let mut image_handle = vk::Image::null(); let mut allocation_handle = Allocation::null(); let allocation_info = allocation_info.map(|i| i as *mut AllocationInfo).unwrap_or(std::ptr::null_mut()); let result = sys::vmaCreateImage(self.handle, image_create_info, allocation_create_info, &mut image_handle, &mut allocation_handle, allocation_info); if result == vk::Result::SUCCESS { Ok((image_handle, allocation_handle)) } else { Err(result) } } pub unsafe fn destroy_image(&self, image: vk::Image, allocation: Allocation) { sys::vmaDestroyImage(self.handle, image, allocation) } } unsafe impl Send for Allocator {} unsafe impl Sync for Allocator {} impl Drop for Allocator { fn drop(&mut self) { unsafe { sys::vmaDestroyAllocator(self.handle) } } } #[derive(Copy, Clone, Eq, PartialEq)] #[repr(transparent)] pub struct Allocation(*const u8); impl Allocation { pub const fn null() -> Self { Allocation(std::ptr::null()) } pub fn is_null(&self) -> bool { self.0.is_null() } } impl Default for Allocation { fn default() -> Self { Self::null() } } unsafe impl Send for Allocation { } unsafe impl Sync for Allocation { } mod sys { use super::*; #[link(name = "VulkanMemoryAllocator", kind = "static")] extern "C" { pub(super) fn vmaCreateAllocator( p_create_info: *const AllocatorCreateInfo, p_allocator: *mut AllocatorHandle ) -> vk::Result; pub(super) fn vmaDestroyAllocator( p_allocator: AllocatorHandle ); pub(super) fn vmaAllocateMemory( allocator: AllocatorHandle, p_vk_memory_requirements: *const vk::MemoryRequirements, p_create_info: *const AllocationCreateInfo, p_allocation: *mut Allocation, p_allocation_info: *mut AllocationInfo, ) -> vk::Result; pub(super) fn vmaAllocateMemoryPages( allocator: AllocatorHandle, p_vk_memory_requirements: *const vk::MemoryRequirements, p_create_info: *const AllocationCreateInfo, allocation_count: usize, p_allocations: *mut Allocation, p_allocation_info: *mut AllocationInfo, ) -> vk::Result; pub(super) fn vmaFreeMemory( allocator: AllocatorHandle, allocation: Allocation, ); pub(super) fn vmaFreeMemoryPages( allocator: AllocatorHandle, count: usize, p_allocations: *const Allocation, ); pub(super) fn vmaGetAllocationInfo( allocator: AllocatorHandle, allocation: Allocation, p_allocation_info: *mut AllocationInfo, ); pub(super) fn vmaSetAllocationName( allocator: AllocatorHandle, allocation: Allocation, name: *const c_char, ); pub(super) fn vmaCreateBuffer( allocator: AllocatorHandle, p_buffer_create_info: *const vk::BufferCreateInfo, p_allocation_create_info: *const AllocationCreateInfo, p_buffer: *mut vk::Buffer, p_allocation: *mut Allocation, p_allocation_info: *mut AllocationInfo, ) -> vk::Result; pub(super) fn vmaDestroyBuffer( allocator: AllocatorHandle, buffer: vk::Buffer, allocation: Allocation, ); pub(super) fn vmaCreateImage( allocator: AllocatorHandle, p_image_create_info: *const vk::ImageCreateInfo, p_allocation_create_info: *const AllocationCreateInfo, p_image: *mut vk::Image, p_allocation: *mut Allocation, p_allocation_info: *mut AllocationInfo, ) -> vk::Result; pub(super) fn vmaDestroyImage( allocator: AllocatorHandle, image: vk::Image, allocation: Allocation, ); } } ================================================ FILE: core/natives/src/b4d.rs ================================================ use std::ffi::CString; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use ash::vk; use crate::BUILD_INFO; use crate::instance::debug_messenger::RustLogDebugMessenger; use crate::device::init::{create_device, DeviceCreateConfig}; use crate::device::surface::{DeviceSurface, SurfaceSwapchain, SwapchainConfig}; use crate::instance::init::{create_instance, InstanceCreateConfig}; use crate::vk::objects::surface::SurfaceProvider; use crate::prelude::*; use crate::renderer::emulator::{EmulatorRenderer, GlobalImage, GlobalMesh, MeshData}; use crate::renderer::emulator::debug_pipeline::{DebugPipeline, DebugPipelineMode}; use crate::renderer::emulator::mc_shaders::{McUniform, ShaderId, VertexFormat}; use crate::renderer::emulator::PassRecorder; use crate::renderer::emulator::pipeline::{EmulatorPipeline, SwapchainOutput}; use crate::util::format::Format; pub struct Blaze4D { instance: Arc, device: Arc, emulator: Arc, render_config: Mutex, } impl Blaze4D { /// Creates a new Blaze4D instance and starts all engine modules. /// /// The supported vertex formats for the [`EmulatorRenderer`] must be provided here. pub fn new(mut main_window: Box, enable_validation: bool) -> Self { log::info!("Creating Blaze4D instance {:?}", BUILD_INFO); let mut instance_config = InstanceCreateConfig::new( CString::new("Minecraft").unwrap(), vk::make_api_version(0, 0, 1, 0) ); if enable_validation { instance_config.enable_validation(); } instance_config.add_debug_messenger(Box::new(RustLogDebugMessenger::new())); for ext in main_window.get_required_instance_extensions() { instance_config.add_required_extension(&ext); } let instance = create_instance(instance_config).unwrap(); let window_surface = main_window.init(instance.get_entry(), instance.vk()).unwrap(); let mut device_config = DeviceCreateConfig::new(); device_config.require_swapchain(); device_config.add_surface(window_surface); device_config.disable_robustness(); let device = create_device(device_config, instance.clone()).unwrap_or_else(|err| { log::error!("Failed to create device in Blaze4D::new(): {:?}", err); panic!() }); let main_surface = DeviceSurface::new(device.get_functions().clone(), main_window); let emulator = Arc::new(EmulatorRenderer::new(device.clone())); let render_config = Mutex::new(RenderConfig::new(device.clone(), emulator.clone(), main_surface)); Self { instance, device, emulator, render_config, } } /// Configures the current debug mode. Any frame started after calling this function will use /// the specified debug mode until another call to this function is made. /// /// If [`None`] is passed the debug mode is disabled. pub fn set_debug_mode(&self, mode: Option) { self.render_config.lock().unwrap().set_debug_mode(mode); } pub fn create_global_mesh(&self, data: &MeshData) -> Arc { self.emulator.create_global_mesh(data) } pub fn create_global_image(&self, size:Vec2u32, format: &'static Format) -> Arc { self.emulator.create_global_image(size, format) } pub fn create_shader(&self, vertex_format: &VertexFormat, used_uniforms: McUniform) -> ShaderId { self.emulator.create_shader(vertex_format, used_uniforms) } pub fn drop_shader(&self, id: ShaderId) { self.emulator.drop_shader(id); } pub fn try_start_frame(&self, window_size: Vec2u32) -> Option { if let Some(recorder) = self.render_config.lock().unwrap().try_start_frame(&self.emulator, window_size) { Some(recorder) } else { None } } } struct RenderConfig { device: Arc, emulator: Arc, main_surface: Arc, last_rebuild: Instant, current_swapchain: Option>, current_pipeline: Option<(Arc, Arc)>, debug_mode: Option, debug_pipeline: Option<(Arc, Arc)>, } impl RenderConfig { fn new(device: Arc, emulator: Arc, main_surface: Arc) -> Self { Self { device, emulator, main_surface, last_rebuild: Instant::now() - Duration::from_secs(100), current_swapchain: None, current_pipeline: None, debug_mode: Some(DebugPipelineMode::Color), debug_pipeline: None } } fn set_debug_mode(&mut self, mode: Option) { if self.debug_mode != mode { self.debug_mode = mode; self.debug_pipeline = None; } } fn try_start_frame(&mut self, renderer: &EmulatorRenderer, size: Vec2u32) -> Option { let mut force_rebuild = false; // This if block only exists because of wayland if let Some(current) = self.current_swapchain.as_ref() { if current.get_image_size() != size { force_rebuild = true; } } if self.current_swapchain.is_none() || force_rebuild { if !self.try_create_swapchain(size) { return None; } self.current_pipeline = None; self.debug_pipeline = None; } let (pipeline, output) = self.prepare_pipeline(size); let (output, suboptimal) = match output.next_image() { None => { self.current_pipeline = None; self.debug_pipeline = None; self.current_swapchain = None; return None; } Some(result) => result, }; let mut recorder = renderer.start_pass(pipeline.clone()); recorder.use_output(output); if suboptimal { self.current_pipeline = None; self.debug_pipeline = None; self.current_swapchain = None; } Some(recorder) } fn prepare_pipeline(&mut self, output_size: Vec2u32) -> (Arc, &Arc) { if let Some(debug_mode) = &self.debug_mode { if self.debug_pipeline.is_none() { log::info!("No debug pipeline present. Rebuilding for size {:?}", output_size); let pipeline = DebugPipeline::new(self.emulator.clone(), *debug_mode, output_size).unwrap(); let swapchain_output = SwapchainOutput::new(&self.device, pipeline.clone(), self.current_swapchain.as_ref().cloned().unwrap()); self.debug_pipeline = Some((pipeline, swapchain_output)); } let (pipeline, output) = self.debug_pipeline.as_ref().unwrap(); (pipeline.clone(), output) } else { todo!() } } fn try_create_swapchain(&mut self, size: Vec2u32) -> bool { log::info!("Attempting to rebuild swapchain with size {:?}", size); let diff = (self.last_rebuild + Duration::from_millis(50)).saturating_duration_since(Instant::now()); if !diff.is_zero() { // Need to wait std::thread::sleep(diff); } self.last_rebuild = Instant::now(); let config = SwapchainConfig { allow_tearing: true, // We set this to true to unlock fps for testing formats: Box::new([ vk::SurfaceFormatKHR{ format: vk::Format::R8G8B8A8_SRGB, color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR }, vk::SurfaceFormatKHR{ format: vk::Format::B8G8R8A8_SRGB, color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR }, ]), required_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT, optional_usage: vk::ImageUsageFlags::empty(), clipped: true }; match self.main_surface.create_swapchain(&config, size) { Ok(swapchain) => { self.current_swapchain = Some(swapchain); true } Err(err) => { log::info!("Failed to create swapchain of size {:?}: {:?}", size, err); self.current_swapchain = None; false } } } } pub struct B4DVertexFormat { pub topology: vk::PrimitiveTopology, pub stride: u32, pub position: (u32, vk::Format), pub color: Option<(u32, vk::Format)>, pub uv: Option<(u32, vk::Format)>, } ================================================ FILE: core/natives/src/c_api.rs ================================================ use std::panic::catch_unwind; use std::process::exit; use std::sync::Arc; use ash::vk; use crate::b4d::Blaze4D; use crate::glfw_surface::GLFWSurfaceProvider; use crate::prelude::{Mat4f32, UUID, Vec2f32, Vec2u32, Vec3f32, Vec4f32}; use crate::renderer::emulator::{MeshData, PassRecorder, ImmediateMeshId, GlobalMesh, ImageData, GlobalImage, SamplerInfo}; use crate::renderer::emulator::debug_pipeline::DebugPipelineMode; use crate::renderer::emulator::mc_shaders::{McUniform, McUniformData, ShaderId, VertexFormat, VertexFormatEntry}; use crate::util::format::Format; use crate::vk::objects::surface::SurfaceProvider; #[repr(C)] struct NativeMetadata { /// The number of bytes of the size type size_bytes: u32, } const NATIVE_METADATA: NativeMetadata = NativeMetadata { size_bytes: std::mem::size_of::() as u32, }; #[repr(C)] #[derive(Copy, Clone, PartialEq, Eq)] struct CDebugMode(u32); impl CDebugMode { pub const NONE: CDebugMode = CDebugMode(0); pub const DEPTH: CDebugMode = CDebugMode(1); pub const POSITION: CDebugMode = CDebugMode(2); pub const COLOR: CDebugMode = CDebugMode(3); pub const NORMAL: CDebugMode = CDebugMode(4); pub const UV0: CDebugMode = CDebugMode(5); pub const UV1: CDebugMode = CDebugMode(6); pub const UV2: CDebugMode = CDebugMode(7); pub const TEXTURED0: CDebugMode = CDebugMode(8); pub const TEXTURED1: CDebugMode = CDebugMode(9); pub const TEXTURED2: CDebugMode = CDebugMode(10); pub fn to_debug_pipeline_mode(&self) -> Option { match *self { Self::NONE => None, Self::DEPTH => Some(DebugPipelineMode::Depth), Self::POSITION => Some(DebugPipelineMode::Position), Self::COLOR => Some(DebugPipelineMode::Color), Self::NORMAL => Some(DebugPipelineMode::Normal), Self::UV0 => Some(DebugPipelineMode::UV0), Self::UV1 => Some(DebugPipelineMode::UV1), Self::UV2 => Some(DebugPipelineMode::UV2), Self::TEXTURED0 => Some(DebugPipelineMode::Textured0), Self::TEXTURED1 => Some(DebugPipelineMode::Textured1), Self::TEXTURED2 => Some(DebugPipelineMode::Textured2), _ => panic!() } } } #[repr(C)] #[derive(Debug)] struct CPipelineConfiguration { depth_test_enable: u32, depth_compare_op: i32, depth_write_enable: u32, blend_enable: u32, blend_color_op: i32, blend_color_src_factor: i32, blend_color_dst_factor: i32, blend_alpha_op: i32, blend_alpha_src_factor: i32, blend_alpha_dst_factor: i32, } #[repr(C)] #[derive(Debug)] struct CMeshData { vertex_data_ptr: *const u8, vertex_data_len: usize, index_data_ptr: *const u8, index_data_len: usize, vertex_stride: u32, index_count: u32, index_type: i32, primitive_topology: i32, } impl CMeshData { unsafe fn to_mesh_data(&self) -> MeshData { if self.vertex_data_ptr.is_null() { log::error!("Vertex data pointer is null"); panic!(); } if self.index_data_ptr.is_null() { log::error!("Index data pointer is null"); panic!(); } MeshData { vertex_data: std::slice::from_raw_parts(self.vertex_data_ptr, self.vertex_data_len as usize), index_data: std::slice::from_raw_parts(self.index_data_ptr, self.index_data_len as usize), vertex_stride: self.vertex_stride, index_count: self.index_count, index_type: vk::IndexType::from_raw(self.index_type), primitive_topology: vk::PrimitiveTopology::from_raw(self.primitive_topology), } } } #[derive(Debug)] #[repr(C)] struct CVertexFormat { stride: u32, position_offset: u32, position_format: i32, normal_offset: u32, normal_format: i32, color_offset: u32, color_format: i32, uv0_offset: u32, uv0_format: i32, uv1_offset: u32, uv1_format: i32, uv2_offset: u32, uv2_format: i32, has_normal: bool, has_color: bool, has_uv0: bool, has_uv1: bool, has_uv2: bool, } impl CVertexFormat { fn to_vertex_format(&self) -> VertexFormat { let normal = if self.has_normal { Some(VertexFormatEntry { offset: self.normal_offset, format: vk::Format::from_raw(self.normal_format) }) } else { None }; let color = if self.has_color { Some(VertexFormatEntry { offset: self.color_offset, format: vk::Format::from_raw(self.color_format) }) } else { None }; let uv0 = if self.has_uv0 { Some( VertexFormatEntry { offset: self.uv0_offset, format: vk::Format::from_raw(self.uv0_format) }) } else { None }; let uv1 = if self.has_uv1 { Some(VertexFormatEntry { offset: self.uv1_offset, format: vk::Format::from_raw(self.uv1_format) }) } else { None }; let uv2 = if self.has_uv2 { Some(VertexFormatEntry { offset: self.uv2_offset, format: vk::Format::from_raw(self.uv2_format) }) } else { None }; VertexFormat { stride: self.stride, position: VertexFormatEntry { offset: self.position_offset, format: vk::Format::from_raw(self.position_format) }, normal, color, uv0, uv1, uv2 } } } #[repr(C)] struct CImageData { data_ptr: *const u8, data_ptr_len: usize, row_stride: u32, offset: [u32; 2], extent: [u32; 2], } impl CImageData { unsafe fn to_image_data(&self) -> ImageData { if self.data_ptr.is_null() { log::error!("Data pointer is null"); panic!(); } ImageData { data: std::slice::from_raw_parts(self.data_ptr, self.data_ptr_len), row_stride: 0, offset: Vec2u32::new(self.offset[0], self.offset[1]), extent: Vec2u32::new(self.extent[0], self.extent[1]) } } } #[repr(C)] union CMcUniformDataPayload { u32: u32, f32: f32, vec2f32: Vec2f32, vec3f32: Vec3f32, vec4f32: Vec4f32, mat4f32: Mat4f32, } #[repr(C)] struct CMcUniformData { uniform: u64, payload: CMcUniformDataPayload, } impl CMcUniformData { unsafe fn to_mc_uniform_data(&self) -> McUniformData { match McUniform::from_raw(self.uniform) { McUniform::MODEL_VIEW_MATRIX => { McUniformData::ModelViewMatrix(self.payload.mat4f32) }, McUniform::PROJECTION_MATRIX => { McUniformData::ProjectionMatrix(self.payload.mat4f32) }, McUniform::INVERSE_VIEW_ROTATION_MATRIX => { McUniformData::InverseViewRotationMatrix(self.payload.mat4f32) }, McUniform::TEXTURE_MATRIX => { McUniformData::TextureMatrix(self.payload.mat4f32) }, McUniform::SCREEN_SIZE => { McUniformData::ScreenSize(self.payload.vec2f32) }, McUniform::COLOR_MODULATOR => { McUniformData::ColorModulator(self.payload.vec4f32) }, McUniform::LIGHT0_DIRECTION => { McUniformData::Light0Direction(self.payload.vec3f32) }, McUniform::LIGHT1_DIRECTION => { McUniformData::Light1Direction(self.payload.vec3f32) }, McUniform::FOG_START => { McUniformData::FogStart(self.payload.f32) }, McUniform::FOG_END => { McUniformData::FogEnd(self.payload.f32) }, McUniform::FOG_COLOR => { McUniformData::FogColor(self.payload.vec4f32) }, McUniform::FOG_SHAPE => { McUniformData::FogShape(self.payload.u32) }, McUniform::LINE_WIDTH => { McUniformData::LineWidth(self.payload.f32) }, McUniform::GAME_TIME => { McUniformData::GameTime(self.payload.f32) }, McUniform::CHUNK_OFFSET => { McUniformData::ChunkOffset(self.payload.vec3f32) }, _ => { log::error!("Invalid uniform type {:?}", self.uniform); panic!() } } } } #[repr(C)] struct CSamplerInfo { mag_filter: i32, min_filter: i32, mipmap_mode: i32, address_mode_u: i32, address_mode_v: i32, anisotropy_enable: u32, } impl CSamplerInfo { fn to_sampler_info(&self) -> SamplerInfo { SamplerInfo { mag_filter: vk::Filter::from_raw(self.mag_filter), min_filter: vk::Filter::from_raw(self.min_filter), mipmap_mode: vk::SamplerMipmapMode::from_raw(self.mipmap_mode), address_mode_u: vk::SamplerAddressMode::from_raw(self.address_mode_u), address_mode_v: vk::SamplerAddressMode::from_raw(self.address_mode_v), anisotropy_enable: self.anisotropy_enable != 0, } } } /// Returns static information about the natives. #[no_mangle] unsafe extern "C" fn b4d_get_native_metadata() -> *const NativeMetadata { &NATIVE_METADATA } /// Creates a new [`Blaze4D`] instance. /// /// This function will take ownership of the provided surface and vertex format set builder. The /// pointers must not be used again afterwards. #[no_mangle] unsafe extern "C" fn b4d_init(surface: *mut GLFWSurfaceProvider, enable_validation: u32) -> *mut Blaze4D { catch_unwind(|| { if surface.is_null() { log::error!("Passed null surface to b4d_init"); exit(1); } let surface_provider: Box = Box::from_raw(surface); let enable_validation = enable_validation != 0; Box::leak(Box::new(Blaze4D::new(surface_provider, enable_validation))) }).unwrap_or_else(|_| { log::error!("panic in b4d_init"); exit(1); }) } /// Destroys a [`Blaze4D`] instance. #[no_mangle] unsafe extern "C" fn b4d_destroy(b4d: *mut Blaze4D) { catch_unwind(|| { if b4d.is_null() { log::error!("Passed null to b4d_destroy"); exit(1); } Box::from_raw(b4d); }).unwrap_or_else(|_| { log::error!("panic in b4d_destroy"); exit(1); }) } #[no_mangle] unsafe extern "C" fn b4d_set_debug_mode(b4d: *const Blaze4D, mode: CDebugMode) { catch_unwind(|| { let b4d = b4d.as_ref().unwrap_or_else(|| { log::error!("Passed null b4d to b4d_set_debug_mode"); exit(1); }); b4d.set_debug_mode(mode.to_debug_pipeline_mode()); }).unwrap_or_else(|_| { log::error!("panic in b4d_set_debug_mode"); exit(1); }) } #[no_mangle] unsafe extern "C" fn b4d_create_global_mesh(b4d: *const Blaze4D, data: *const CMeshData) -> *mut Arc { catch_unwind(|| { let b4d = b4d.as_ref().unwrap_or_else(|| { log::error!("Passed null b4d to b4d_create_global_mesh"); exit(1); }); let data = data.as_ref().unwrap_or_else(|| { log::error!("Passed null mesh data to b4d_create_global_mesh"); exit(1); }); let mesh_data = data.to_mesh_data(); Box::leak(Box::new(b4d.create_global_mesh(&mesh_data))) }).unwrap_or_else(|_| { log::error!("panic in b4d_create_global_mesh"); exit(1); }) } #[no_mangle] unsafe extern "C" fn b4d_destroy_global_mesh(mesh: *mut Arc) { catch_unwind(|| { if mesh.is_null() { log::error!("Passed null mesh to b4d_destroy_global_mesh"); } Box::from_raw(mesh); }).unwrap_or_else(|_| { log::error!("panic in b4d_destroy_global_mesh"); exit(1); }) } #[no_mangle] unsafe extern "C" fn b4d_create_global_image(b4d: *const Blaze4D, width: u32, height: u32, format: i32) -> *mut Arc { catch_unwind(|| { let b4d = b4d.as_ref().unwrap_or_else(|| { log::error!("Passed null b4d to b4d_create_global_image"); exit(1); }); let size = Vec2u32::new(width, height); let format = Format::format_for(vk::Format::from_raw(format)); Box::leak(Box::new(b4d.create_global_image(size, format))) }).unwrap_or_else(|_| { log::error!("panic in b4d_create_global_image"); exit(1); }) } #[no_mangle] unsafe extern "C" fn b4d_update_global_image(image: *mut Arc, writes: *const CImageData, count: u32) { catch_unwind(|| { let image = image.as_ref().unwrap_or_else(|| { log::error!("Passed null image to b4d_update_global_image"); exit(1); }); if writes.is_null() { log::error!("Passed null writes to b4d_update_global_image"); exit(1); } let writes = std::slice::from_raw_parts(writes, count as usize); let writes: Box<_> = writes.iter().map(|w| w.to_image_data()).collect(); image.update_regions(writes.as_ref()); }).unwrap_or_else(|_| { log::error!("panic in b4d_update_global_image"); exit(1); }) } #[no_mangle] unsafe extern "C" fn b4d_destroy_global_image(image: *mut Arc) { catch_unwind(|| { if image.is_null() { log::error!("Passed null image to b4d_destroy_global_image"); } Box::from_raw(image); }).unwrap_or_else(|_| { log::error!("panic in b4d_destroy_global_image"); exit(1); }) } #[no_mangle] unsafe extern "C" fn b4d_create_shader(b4d: *const Blaze4D, vertex_format: *const CVertexFormat, used_uniforms: u64) -> u64 { catch_unwind(|| { let b4d = b4d.as_ref().unwrap_or_else(|| { log::error!("Passed null b4d to b4d_create_shader"); exit(1); }); let vertex_format = vertex_format.as_ref().unwrap_or_else(|| { log::error!("Passed null vertex_format to b4d_create_shader"); exit(1); }); let vertex_format = vertex_format.to_vertex_format(); let mc_uniform = McUniform::from_raw(used_uniforms); b4d.create_shader(&vertex_format, mc_uniform).as_uuid().get_raw() }).unwrap_or_else(|_| { log::error!("panic in b4d_create_shader"); exit(1); }) } #[no_mangle] unsafe extern "C" fn b4d_destroy_shader(b4d: *const Blaze4D, shader_id: u64) { catch_unwind(|| { let b4d = b4d.as_ref().unwrap_or_else(|| { log::error!("Passed null b4d to b4d_destroy_shader"); exit(1); }); b4d.drop_shader(ShaderId::from_uuid(UUID::from_raw(shader_id))); }).unwrap_or_else(|_| { log::error!("panic in b4d_destroy_shader"); exit(1); }) } /// Calls [`Blaze4D::try_start_frame`]. /// /// If [`Blaze4D::try_start_frame`] returns [`None`] this function returns null. #[no_mangle] unsafe extern "C" fn b4d_start_frame(b4d: *mut Blaze4D, window_width: u32, window_height: u32) -> *mut PassRecorder { catch_unwind(|| { let b4d = b4d.as_mut().unwrap_or_else(|| { log::error!("Passed null b4d to b4d_start_frame"); exit(1); }); let frame = b4d.try_start_frame(Vec2u32::new(window_width, window_height)); frame.map_or(std::ptr::null_mut(), |recorder| { Box::leak(Box::new(recorder)) }) }).unwrap_or_else(|_| { log::error!("panic in b4d_start_frame"); exit(1); }) } #[no_mangle] unsafe extern "C" fn b4d_pass_update_uniform(pass: *mut PassRecorder, data: *const CMcUniformData, shader_id: u64) { catch_unwind(|| { let pass = pass.as_mut().unwrap_or_else(|| { log::error!("Passed null pass to b4d_pass_update_dev_uniform"); exit(1); }); let data = data.as_ref().unwrap_or_else(|| { log::error!("Passed null data to b4d_pass_update_dev_uniform"); exit(1); }); let data = data.to_mc_uniform_data(); let shader_id = ShaderId::from_uuid(UUID::from_raw(shader_id)); pass.update_uniform(&data, shader_id); }).unwrap_or_else(|_| { log::error!("panic in b4d_pass_update_dev_uniform"); exit(1); }) } #[no_mangle] unsafe extern "C" fn b4d_pass_update_texture(pass: *mut PassRecorder, index: u32, image: *const Arc, sampler_info: *const CSamplerInfo, shader_id: u64) { catch_unwind(|| { let pass = pass.as_mut().unwrap_or_else(|| { log::error!("Passed null pass to b4d_pass_update_texture"); exit(1); }); let image = image.as_ref().unwrap_or_else(|| { log::error!("Passed null image to b4d_pass_update_texture"); exit(1); }); let sampler_info = sampler_info.as_ref().unwrap_or_else(|| { log::error!("Passed null sampler_info to b4d_pass_update_texture"); exit(1); }); let sampler_info = sampler_info.to_sampler_info(); let shader_id = ShaderId::from_uuid(UUID::from_raw(shader_id)); pass.update_texture(index, image, &sampler_info, shader_id); }).unwrap_or_else(|_| { log::error!("panic in b4d_pass_update_texture"); exit(1); }) } #[no_mangle] unsafe extern "C" fn b4d_pass_draw_global(pass: *mut PassRecorder, mesh: *const Arc, shader_id: u64, depth_write_enable: u32) { catch_unwind(|| { let pass = pass.as_mut().unwrap_or_else(|| { log::error!("Passed null pass to b4d_pass_draw_global"); exit(1); }); let mesh = mesh.as_ref().unwrap_or_else(|| { log::error!("Passed null mesh to b4d_pass_draw_global"); exit(1); }); let shader_id = ShaderId::from_uuid(UUID::from_raw(shader_id)); let depth_write_enable = if depth_write_enable == 1 { true } else { false }; pass.draw_global(mesh.clone(), shader_id, depth_write_enable); }).unwrap_or_else(|_| { log::error!("panic in b4d_pass_draw_global"); exit(1); }) } #[no_mangle] unsafe extern "C" fn b4d_pass_upload_immediate(pass: *mut PassRecorder, data: *const CMeshData) -> u32 { catch_unwind(|| { let pass = pass.as_mut().unwrap_or_else(|| { log::error!("Passed null pass to b4d_pass_upload_immediate"); exit(1); }); let data = data.as_ref().unwrap_or_else(|| { log::error!("Passed null mesh data to b4d_pass_upload_immediate"); exit(1); }); let mesh_data = data.to_mesh_data(); pass.upload_immediate(&mesh_data).get_raw() }).unwrap_or_else(|_| { log::error!("panic in b4d_pass_upload_immediate"); exit(1); }) } #[no_mangle] unsafe extern "C" fn b4d_pass_draw_immediate(pass: *mut PassRecorder, id: u32, shader_id: u64, depth_write_enable: u32) { catch_unwind(|| { let pass = pass.as_mut().unwrap_or_else(|| { log::error!("Passed null pass to b4d_pass_draw_immediate"); exit(1); }); let shader_id = ShaderId::from_uuid(UUID::from_raw(shader_id)); let depth_write_enable = if depth_write_enable == 1 { true } else { false }; pass.draw_immediate(ImmediateMeshId::form_raw(id), shader_id, depth_write_enable); }).unwrap_or_else(|_| { log::error!("panic in b4d_pass_draw_immediate"); exit(1); }) } #[no_mangle] unsafe extern "C" fn b4d_end_frame(recorder: *mut PassRecorder) { catch_unwind(|| { if recorder.is_null() { log::error!("Passed null to b4d_end_frame"); exit(1); } Box::from_raw(recorder); }).unwrap_or_else(|_| { log::error!("panic in b4d_end_frame"); exit(1); }) } ================================================ FILE: core/natives/src/c_log.rs ================================================ //! Forwards rust logs to some external handler. use std::panic::catch_unwind; use std::process::exit; use log::{Level, LevelFilter, Log, Metadata, Record}; // target_ptr, msg_ptr, target_len, msg_len, level type PfnLog = unsafe extern "C" fn(*const u8, *const u8, u32, u32, u32); struct CLogger { pfn: PfnLog, } impl CLogger { fn new(pfn: PfnLog) -> Self { Self { pfn, } } fn log_internal(&self, target: &str, message: &str, level: Level) { let level = match level { Level::Error => 4, Level::Warn => 3, Level::Info => 2, Level::Debug => 1, Level::Trace => 0, }; let target = target.as_bytes(); let message = message.as_bytes(); unsafe { (self.pfn)(target.as_ptr(), message.as_ptr(), target.len() as u32, message.len() as u32, level); } } } impl Log for CLogger { fn enabled(&self, _: &Metadata) -> bool { true } fn log(&self, record: &Record) { if let Some(msg) = record.args().as_str() { self.log_internal(record.target(), msg, record.level()); } else { self.log_internal(record.target(), &record.args().to_string(), record.level()); } } fn flush(&self) { } } #[no_mangle] unsafe extern "C" fn b4d_init_external_logger(pfn: PfnLog) { catch_unwind(|| { let logger = Box::new(CLogger::new(pfn)); log::set_boxed_logger(logger).unwrap_or_else(|err| { println!("Failed to set logger in b4d_init_external_logger. {:?}", err); exit(1); }); log::set_max_level(LevelFilter::Info); }).unwrap_or_else(|_| { // Log is not going to work here so we use print instead println!("panic in b4d_init_external_logger"); exit(1); }) } ================================================ FILE: core/natives/src/device/device.rs ================================================ use core::panic::{UnwindSafe, RefUnwindSafe}; use std::cmp::Ordering; use std::sync::{Arc, Mutex, MutexGuard}; use ash::prelude::VkResult; use ash::vk; use crate::allocator::Allocator; use crate::device::device_utils::DeviceUtils; use crate::instance::instance::InstanceContext; use crate::prelude::*; pub struct DeviceFunctions { pub instance: Arc, pub physical_device: vk::PhysicalDevice, pub vk: ash::Device, pub synchronization_2_khr: ash::extensions::khr::Synchronization2, pub timeline_semaphore_khr: ash::extensions::khr::TimelineSemaphore, pub push_descriptor_khr: ash::extensions::khr::PushDescriptor, pub swapchain_khr: Option, pub maintenance_4_khr: Option, } impl Drop for DeviceFunctions { fn drop(&mut self) { unsafe { self.vk.destroy_device(None); } } } pub struct DeviceContext { id: NamedUUID, functions: Arc, main_queue: Arc, async_compute_queue: Option>, async_transfer_queue: Option>, allocator: Arc, utils: Arc, } impl DeviceContext { pub(crate) fn new( functions: Arc, main_queue: Arc, async_compute_queue: Option>, async_transfer_queue: Option>, ) -> Arc { let allocator = Arc::new(Allocator::new(functions.clone()).unwrap()); let utils = DeviceUtils::new(functions.clone(), allocator.clone()); Arc::new(Self { id: NamedUUID::with_str("Device"), functions, main_queue, async_compute_queue, async_transfer_queue, allocator, utils }) } pub fn get_uuid(&self) -> &NamedUUID { &self.id } pub fn get_entry(&self) -> &ash::Entry { self.functions.instance.get_entry() } pub fn get_instance(&self) -> &Arc { &self.functions.instance } pub fn get_functions(&self) -> &Arc { &self.functions } pub fn vk(&self) -> &ash::Device { &self.functions.vk } pub fn synchronization_2_khr(&self) -> &ash::extensions::khr::Synchronization2 { &self.functions.synchronization_2_khr } pub fn timeline_semaphore_khr(&self) -> &ash::extensions::khr::TimelineSemaphore { &self.functions.timeline_semaphore_khr } pub fn push_descriptor_khr(&self) -> &ash::extensions::khr::PushDescriptor { &self.functions.push_descriptor_khr } pub fn swapchain_khr(&self) -> Option<&ash::extensions::khr::Swapchain> { self.functions.swapchain_khr.as_ref() } pub fn maintenance_4(&self) -> Option<&ash::extensions::khr::Maintenance4> { self.functions.maintenance_4_khr.as_ref() } pub fn get_main_queue(&self) -> &Arc { &self.main_queue } pub fn get_async_compute_queue(&self) -> Option<&Arc> { self.async_compute_queue.as_ref() } pub fn get_async_transfer_queue(&self) -> Option<&Arc> { self.async_transfer_queue.as_ref() } pub fn get_allocator(&self) -> &Arc { &self.allocator } pub fn get_utils(&self) -> &Arc { &self.utils } } impl PartialEq for DeviceContext { fn eq(&self, other: &Self) -> bool { self.id.eq(&other.id) } } impl Eq for DeviceContext { } impl PartialOrd for DeviceContext { fn partial_cmp(&self, other: &Self) -> Option { self.id.partial_cmp(&other.id) } } impl Ord for DeviceContext { fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) } } assert_impl_all!(DeviceContext: Send, Sync, UnwindSafe, RefUnwindSafe); pub struct Queue { functions: Arc, queue: Mutex, family: u32, } impl Queue { pub(super) fn new(functions: Arc, family: u32, index: u32) -> Self { let queue = unsafe { functions.vk.get_device_queue(family, index) }; Self { functions, queue: Mutex::new(queue), family } } pub unsafe fn submit(&self, submits: &[vk::SubmitInfo], fence: Option) -> VkResult<()> { let fence = fence.unwrap_or(vk::Fence::null()); let queue = self.queue.lock().unwrap(); self.functions.vk.queue_submit(*queue, submits, fence) } pub unsafe fn submit_2(&self, submits: &[vk::SubmitInfo2], fence: Option) -> VkResult<()> { let fence = fence.unwrap_or(vk::Fence::null()); let queue = self.queue.lock().unwrap(); self.functions.synchronization_2_khr.queue_submit2(*queue, submits, fence) } pub unsafe fn wait_idle(&self) -> VkResult<()> { let queue = self.queue.lock().unwrap(); self.functions.vk.queue_wait_idle(*queue) } pub unsafe fn bind_sparse(&self, bindings: &[vk::BindSparseInfo], fence: Option) -> VkResult<()> { let fence = fence.unwrap_or(vk::Fence::null()); let queue = self.queue.lock().unwrap(); self.functions.vk.queue_bind_sparse(*queue, bindings, fence) } // TODO this also needs to lock the swapchain. How do we properly deal with this? pub unsafe fn present(&self, present_info: &vk::PresentInfoKHR) -> VkResult { let queue = self.queue.lock().unwrap(); self.functions.swapchain_khr.as_ref().unwrap().queue_present(*queue, present_info) } pub fn lock_queue(&self) -> MutexGuard { self.queue.lock().unwrap() } pub fn get_queue_family_index(&self) -> u32 { self.family } } assert_impl_all!(Queue: Send, Sync, UnwindSafe, RefUnwindSafe); ================================================ FILE: core/natives/src/device/device_utils.rs ================================================ use std::ffi::CStr; use std::iter::repeat; use std::sync::{Arc, Weak}; use ash::prelude::VkResult; use ash::vk; use bytemuck::cast_slice; use include_bytes_aligned::include_bytes_aligned; use crate::allocator::Allocator; use crate::prelude::*; pub fn create_shader_from_bytes(device: &DeviceFunctions, code: &[u8]) -> VkResult { let info = vk::ShaderModuleCreateInfo::builder() .code(cast_slice(code)); unsafe { device.vk.create_shader_module(&info, None) } } pub struct DeviceUtils { blit_utils: BlitUtils, } impl DeviceUtils { pub fn new(device: Arc, _: Arc) -> Arc { Arc::new_cyclic(|weak| { Self { blit_utils: BlitUtils::new(weak.clone(), device) } }) } pub fn blit_utils(&self) -> &BlitUtils { &self.blit_utils } } pub struct BlitUtils { utils: Weak, device: Arc, vertex_shader: vk::ShaderModule, fragment_shader: vk::ShaderModule, sampler: vk::Sampler, set_layout: vk::DescriptorSetLayout, pipeline_layout: vk::PipelineLayout, } impl BlitUtils { fn new(utils: Weak, device: Arc) -> Self { let vertex_shader = create_shader_from_bytes(&device, FULL_SCREEN_QUAD_VERTEX_SHADER).unwrap(); let fragment_shader = create_shader_from_bytes(&device, BLIT_FRAGMENT_SHADER).unwrap(); let sampler = Self::create_sampler(&device); let set_layout = Self::create_descriptor_set_layout(&device, sampler); let pipeline_layout = Self::create_pipeline_layout(&device, set_layout); Self { utils, device, vertex_shader, fragment_shader, sampler, set_layout, pipeline_layout } } pub fn create_blit_pass(&self, dst_format: vk::Format, load_op: vk::AttachmentLoadOp, initial_layout: vk::ImageLayout, final_layout: vk::ImageLayout) -> BlitPass { let render_pass = self.create_render_pass(dst_format, load_op, initial_layout, final_layout); let pipeline = self.create_pipeline(render_pass); BlitPass { utils: self.utils.upgrade().unwrap(), render_pass, pipeline, } } fn create_render_pass(&self, dst_format: vk::Format, load_op: vk::AttachmentLoadOp, initial_layout: vk::ImageLayout, final_layout: vk::ImageLayout) -> vk::RenderPass { let attachment = vk::AttachmentDescription::builder() .format(dst_format) .samples(vk::SampleCountFlags::TYPE_1) .load_op(load_op) .store_op(vk::AttachmentStoreOp::STORE) .initial_layout(initial_layout) .final_layout(final_layout); let attachment_reference = vk::AttachmentReference { attachment: 0, layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL }; let subpass = vk::SubpassDescription::builder() .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) .color_attachments(std::slice::from_ref(&attachment_reference)); let info = vk::RenderPassCreateInfo::builder() .attachments(std::slice::from_ref(&attachment)) .subpasses(std::slice::from_ref(&subpass)); unsafe { self.device.vk.create_render_pass(&info, None) }.unwrap() } fn create_pipeline(&self, render_pass: vk::RenderPass) -> vk::Pipeline { let shader_stages = [ vk::PipelineShaderStageCreateInfo::builder() .stage(vk::ShaderStageFlags::VERTEX) .module(self.vertex_shader) .name(CStr::from_bytes_with_nul(b"main\0").unwrap()) .build(), vk::PipelineShaderStageCreateInfo::builder() .stage(vk::ShaderStageFlags::FRAGMENT) .module(self.fragment_shader) .name(CStr::from_bytes_with_nul(b"main\0").unwrap()) .build() ]; let input_state = vk::PipelineVertexInputStateCreateInfo::builder(); let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::builder() .topology(vk::PrimitiveTopology::TRIANGLE_STRIP); let viewport = vk::PipelineViewportStateCreateInfo::builder() .viewport_count(1) .scissor_count(1); let rasterization_state = vk::PipelineRasterizationStateCreateInfo::builder() .depth_clamp_enable(false) .rasterizer_discard_enable(false) .polygon_mode(vk::PolygonMode::FILL) .cull_mode(vk::CullModeFlags::NONE) .front_face(vk::FrontFace::CLOCKWISE) .depth_bias_enable(false) .line_width(1.0); let multisample_state = vk::PipelineMultisampleStateCreateInfo::builder() .rasterization_samples(vk::SampleCountFlags::TYPE_1) .sample_shading_enable(false); let depth_stencil_state = vk::PipelineDepthStencilStateCreateInfo::builder() .depth_test_enable(false) .depth_write_enable(false); let attachment = vk::PipelineColorBlendAttachmentState::builder() .blend_enable(false) .color_write_mask(vk::ColorComponentFlags::RGBA); let color_blend = vk::PipelineColorBlendStateCreateInfo::builder() .logic_op_enable(false) .attachments(std::slice::from_ref(&attachment)); let dynamic_states = [ vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR ]; let dynamic_state = vk::PipelineDynamicStateCreateInfo::builder() .dynamic_states(&dynamic_states); let info = vk::GraphicsPipelineCreateInfo::builder() .stages(&shader_stages) .vertex_input_state(&input_state) .input_assembly_state(&input_assembly) .viewport_state(&viewport) .rasterization_state(&rasterization_state) .multisample_state(&multisample_state) .depth_stencil_state(&depth_stencil_state) .color_blend_state(&color_blend) .dynamic_state(&dynamic_state) .layout(self.pipeline_layout) .render_pass(render_pass); let pipeline = * unsafe { self.device.vk.create_graphics_pipelines(vk::PipelineCache::null(), std::slice::from_ref(&info), None) }.unwrap().get(0).unwrap(); pipeline } fn create_sampler(device: &DeviceFunctions) -> vk::Sampler { let info = vk::SamplerCreateInfo::builder() .mag_filter(vk::Filter::LINEAR) .min_filter(vk::Filter::LINEAR) .mipmap_mode(vk::SamplerMipmapMode::NEAREST) .address_mode_u(vk::SamplerAddressMode::REPEAT) .address_mode_v(vk::SamplerAddressMode::REPEAT) .address_mode_w(vk::SamplerAddressMode::REPEAT) .anisotropy_enable(false) .compare_enable(false) .unnormalized_coordinates(false); unsafe { device.vk.create_sampler(&info, None) }.unwrap() } fn create_descriptor_set_layout(device: &DeviceFunctions, sampler: vk::Sampler) -> vk::DescriptorSetLayout { let binding = vk::DescriptorSetLayoutBinding::builder() .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) .descriptor_count(1) .stage_flags(vk::ShaderStageFlags::FRAGMENT) .immutable_samplers(std::slice::from_ref(&sampler)); let info = vk::DescriptorSetLayoutCreateInfo::builder() .bindings(std::slice::from_ref(&binding)); unsafe { device.vk.create_descriptor_set_layout(&info, None) }.unwrap() } fn create_pipeline_layout(device: &DeviceFunctions, set_layout: vk::DescriptorSetLayout) -> vk::PipelineLayout { let info = vk::PipelineLayoutCreateInfo::builder() .set_layouts(std::slice::from_ref(&set_layout)); unsafe { device.vk.create_pipeline_layout(&info, None) }.unwrap() } } impl Drop for BlitUtils { fn drop(&mut self) { unsafe { self.device.vk.destroy_pipeline_layout(self.pipeline_layout, None); self.device.vk.destroy_descriptor_set_layout(self.set_layout, None); self.device.vk.destroy_sampler(self.sampler, None); self.device.vk.destroy_shader_module(self.fragment_shader, None); self.device.vk.destroy_shader_module(self.vertex_shader, None); } } } pub struct BlitPass { utils: Arc, render_pass: vk::RenderPass, pipeline: vk::Pipeline, } impl BlitPass { /// Allocates and writes descriptor sets for a collection of image views. /// /// The descriptor sets are fully owned by the calling code after this function returns. pub fn create_descriptor_sets(&self, pool: vk::DescriptorPool, image_views: &[vk::ImageView]) -> VkResult> { let layouts: Box<[_]> = repeat(self.utils.blit_utils.set_layout).take(image_views.len()).collect(); let info = vk::DescriptorSetAllocateInfo::builder() .descriptor_pool(pool) .set_layouts(layouts.as_ref()); let sets = unsafe { self.utils.blit_utils.device.vk.allocate_descriptor_sets(&info) }?; let image_writes: Box<[_]> = image_views.iter().map(|view| { vk::DescriptorImageInfo::builder() .image_view(*view) .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) }).collect(); let writes: Box<[_]> = sets.iter().zip(image_writes.iter()).map(|(set, info)| { vk::WriteDescriptorSet::builder() .dst_set(*set) .dst_binding(0) .dst_array_element(0) .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) .image_info(std::slice::from_ref(info)) .build() }).collect(); unsafe { self.utils.blit_utils.device.vk.update_descriptor_sets(writes.as_ref(), &[]) }; // We had to build so we need to make sure lifetimes are guaranteed drop(image_writes); Ok(sets) } /// Creates a framebuffer for a image view which can be used for this blit operation. /// /// The framebuffer is fully owned by the calling code and must be destroyed by it. In particular /// it must be guaranteed that the framebuffer is destroyed before this struct is dropped. pub fn create_framebuffer(&self, image_view: vk::ImageView, size: Vec2u32) -> VkResult { let info = vk::FramebufferCreateInfo::builder() .render_pass(self.render_pass) .attachments(std::slice::from_ref(&image_view)) .width(size[0]) .height(size[1]) .layers(1); unsafe { self.utils.blit_utils.device.vk.create_framebuffer(&info, None) } } /// Records a blit operation using a descriptor set and framebuffer previously created from this /// struct. No memory barriers are generated. /// /// The framebuffer image will be used in the COLOR_ATTACHMENT_OUTPUT stage and the sampled image /// in the FRAGMENT_SHADER stage. The sampled image must be in the SHADER_READ_OPTIMAL layout. pub fn record_blit(&self, command_buffer: vk::CommandBuffer, descriptor_set: vk::DescriptorSet, framebuffer: vk::Framebuffer, size: Vec2u32, clear_value: Option<&vk::ClearValue>) { let device = &self.utils.blit_utils.device; let mut info = vk::RenderPassBeginInfo::builder() .render_pass(self.render_pass) .framebuffer(framebuffer) .render_area(vk::Rect2D { offset: vk::Offset2D { x: 0, y: 0 }, extent: vk::Extent2D { width: size[0], height: size[1] } }); if let Some(clear_value) = clear_value { info = info.clear_values(std::slice::from_ref(clear_value)) } let viewport = vk::Viewport::builder() .x(0f32) .y(0f32) .width(size[0] as f32) .height(size[1] as f32) .min_depth(0.0) .max_depth(1.0); let scissor = vk::Rect2D { offset: vk::Offset2D{ x: 0, y: 0 }, extent: vk::Extent2D{ width: size[0], height: size[1] } }; unsafe { device.vk.cmd_set_viewport(command_buffer, 0, std::slice::from_ref(&viewport)); device.vk.cmd_set_scissor(command_buffer, 0, std::slice::from_ref(&scissor)); device.vk.cmd_begin_render_pass(command_buffer, &info, vk::SubpassContents::INLINE); device.vk.cmd_bind_pipeline(command_buffer, vk::PipelineBindPoint::GRAPHICS, self.pipeline); device.vk.cmd_bind_descriptor_sets( command_buffer, vk::PipelineBindPoint::GRAPHICS, self.utils.blit_utils.pipeline_layout, 0, std::slice::from_ref(&descriptor_set), &[] ); device.vk.cmd_draw(command_buffer, 4, 1, 0, 0); device.vk.cmd_end_render_pass(command_buffer); } } pub fn get_device(&self) -> &Arc { &self.utils.blit_utils.device } } impl Drop for BlitPass { fn drop(&mut self) { unsafe { self.utils.blit_utils.device.vk.destroy_pipeline(self.pipeline, None); self.utils.blit_utils.device.vk.destroy_render_pass(self.render_pass, None); } } } static FULL_SCREEN_QUAD_VERTEX_SHADER: &'static [u8] = include_bytes_aligned!(4, concat!(env!("B4D_RESOURCE_DIR"), "utils/full_screen_quad_vert.spv")); static BLIT_FRAGMENT_SHADER: &'static [u8] = include_bytes_aligned!(4, concat!(env!("B4D_RESOURCE_DIR"), "utils/blit_frag.spv")); ================================================ FILE: core/natives/src/device/init.rs ================================================ use std::collections::HashSet; use std::ffi::{CStr, CString}; use std::os::raw::c_char; use std::sync::Arc; use ash::vk; use bumpalo::Bump; use vk_profiles_rs::{vp, VulkanProfiles}; use crate::device::device::{DeviceFunctions, Queue}; use crate::instance::instance::{InstanceContext, VulkanVersion}; use crate::prelude::*; #[derive(Debug)] pub struct DeviceCreateConfig { used_surfaces: Vec, disable_robustness: bool, required_extensions: HashSet, } impl DeviceCreateConfig { pub fn new() -> Self { Self { used_surfaces: Vec::new(), required_extensions: HashSet::new(), disable_robustness: false, } } pub fn add_surface(&mut self, surface: vk::SurfaceKHR) { self.used_surfaces.push(surface); } pub fn disable_robustness(&mut self) { self.disable_robustness = true; } pub fn add_required_extension(&mut self, extension: &CStr) { self.required_extensions.insert(CString::from(extension)); } pub fn require_swapchain(&mut self) { self.required_extensions.insert(CString::new("VK_KHR_swapchain").unwrap()); } } #[derive(Debug)] pub enum DeviceCreateError { Vulkan(vk::Result), NoSupportedDevice, SurfaceNotFound, } impl From for DeviceCreateError { fn from(result: vk::Result) -> Self { DeviceCreateError::Vulkan(result) } } pub fn create_device(config: DeviceCreateConfig, instance: Arc) -> Result, DeviceCreateError> { log::info!("Creating vulkan device with config: {:?}", config); let vk_vp = VulkanProfiles::linked(); let has_swapchain = config.required_extensions.contains(&CString::new("VK_KHR_swapchain").unwrap()); let allocator = Bump::new(); let (device_config, device_create_info, physical_device) = filter_devices( unsafe { instance.vk().enumerate_physical_devices()? }, &instance, &vk_vp, &config, &allocator )?; let priority = 1f32; let mut queue_create_infos = Vec::with_capacity(3); queue_create_infos.push(vk::DeviceQueueCreateInfo::builder() .queue_family_index(device_config.main_queue_family) .queue_priorities(std::slice::from_ref(&priority)) .build() ); if let Some(family) = &device_config.async_compute_family { queue_create_infos.push(vk::DeviceQueueCreateInfo::builder() .queue_family_index(*family) .queue_priorities(std::slice::from_ref(&priority)) .build() ); } if let Some(family) = &device_config.async_transfer_family { queue_create_infos.push(vk::DeviceQueueCreateInfo::builder() .queue_family_index(*family) .queue_priorities(std::slice::from_ref(&priority)) .build() ); } let device_create_info = device_create_info.queue_create_infos(queue_create_infos.as_slice()); let mut flags = vp::DeviceCreateFlagBits::MERGE_EXTENSIONS | vp::DeviceCreateFlagBits::OVERRIDE_FEATURES; if config.disable_robustness { flags |= vp::DeviceCreateFlagBits::DISABLE_ROBUST_ACCESS; } let vp_device_create_info = vp::DeviceCreateInfo::builder() .profile(instance.get_profile()) .create_info(&device_create_info) .flags(flags); let selected_properties = unsafe { instance.vk().get_physical_device_properties(physical_device) }; let selected_device_name = unsafe { CStr::from_ptr(selected_properties.device_name.as_ptr()) }; log::info!("Selected device {:?} with config {:?}", selected_device_name, device_config); let device = unsafe { vk_vp.create_device(instance.vk(), physical_device, &vp_device_create_info, None)? }; let synchronization_2_khr = ash::extensions::khr::Synchronization2::new(instance.vk(), &device); let timeline_semaphore_khr = ash::extensions::khr::TimelineSemaphore::new(instance.vk(), &device); let push_descriptor_khr = ash::extensions::khr::PushDescriptor::new(instance.vk(), &device); let swapchain_khr = if has_swapchain { Some(ash::extensions::khr::Swapchain::new(instance.vk(), &device)) } else { None }; let maintenance_4_khr = if device_config.has_maintenance4 { Some(ash::extensions::khr::Maintenance4::new(instance.vk(), &device)) } else { None }; let functions = Arc::new(DeviceFunctions { instance, physical_device, vk: device, synchronization_2_khr, timeline_semaphore_khr, push_descriptor_khr, swapchain_khr, maintenance_4_khr }); let main_queue = Arc::new(Queue::new(functions.clone(), device_config.main_queue_family, 0)); let async_compute_queue = device_config.async_compute_family.map(|family| { Arc::new(Queue::new(functions.clone(), family, 0)) }); let async_transfer_queue = device_config.async_transfer_family.map(|family| { Arc::new(Queue::new(functions.clone(), family, 0)) }); Ok(DeviceContext::new( functions, main_queue, async_compute_queue, async_transfer_queue )) } fn filter_devices<'a>( devices: Vec, instance: &InstanceContext, vk_vp: &VulkanProfiles, config: &DeviceCreateConfig, allocator: &'a Bump ) -> Result<(DeviceConfigInfo, vk::DeviceCreateInfoBuilder<'a>, vk::PhysicalDevice), DeviceCreateError> { let profile = instance.get_profile(); let mut best_device: Option<(DeviceConfigInfo, vk::DeviceCreateInfoBuilder, vk::PhysicalDevice)> = None; for device in devices { if let Some(mut configurator) = DeviceConfigurator::new( instance, vk_vp, config, profile, device, allocator )? { if let Some(device_config) = configure_device(&mut configurator)? { best_device = if let Some(old) = best_device { if device_config.rating > old.0.rating { Some((device_config, configurator.build(), device)) } else { Some(old) } } else { Some((device_config, configurator.build(), device)) } } } } best_device.ok_or(DeviceCreateError::NoSupportedDevice) } struct DeviceConfigurator<'a, 'b> { instance: &'a InstanceContext, config: &'a DeviceCreateConfig, physical_device: vk::PhysicalDevice, device_name: CString, available_extensions: HashSet, used_extensions: HashSet, queue_family_surface_support: Box<[bool]>, alloc: &'b Bump, create_info: vk::DeviceCreateInfoBuilder<'b>, } impl<'a, 'b> DeviceConfigurator<'a, 'b> { fn new(instance: &'a InstanceContext, vk_vp: &VulkanProfiles, config: &'a DeviceCreateConfig, profile: &vp::ProfileProperties, physical_device: vk::PhysicalDevice, alloc: &'b Bump) -> Result, DeviceCreateError> { let properties = unsafe { instance.vk().get_physical_device_properties(physical_device) }; let device_name = CString::from(unsafe { CStr::from_ptr(properties.device_name.as_ptr()) }); log::info!("Checking physical device {:?} {:?}", device_name, VulkanVersion::from_raw(properties.api_version)); let used_extensions = config.required_extensions.clone(); let available_extensions: HashSet<_> = unsafe { instance.vk().enumerate_device_extension_properties(physical_device)? } .into_iter().map(|ext| { CString::from(unsafe { CStr::from_ptr(ext.extension_name.as_ptr()) }) }).collect(); log::info!("Physical device {:?} has extensions: {:?}", device_name, available_extensions); if !used_extensions.is_subset(&available_extensions) { log::info!("Physical device {:?} is missing required extensions {:?}", device_name, used_extensions.difference(&available_extensions) ); return Ok(None); } if !unsafe { vk_vp.get_physical_device_profile_support(instance.vk(), physical_device, profile) }? { log::info!("Physical device {:?} does not support used profile", device_name); return Ok(None); } let queue_family_count = unsafe { instance.vk().get_physical_device_queue_family_properties(physical_device) }.len(); let mut queue_family_surface_support: Box<[bool]> = std::iter::repeat(true).take(queue_family_count).collect(); for surface in &config.used_surfaces { let surface_khr = instance.surface_khr().unwrap(); for (index, support) in queue_family_surface_support.iter_mut().enumerate() { if !unsafe { surface_khr.get_physical_device_surface_support(physical_device, index as u32, *surface) }? { *support = false; } } } if !queue_family_surface_support.iter().any(|b| *b) { log::info!("Physical device {:?} does not contain queue family which supports all surfaces", device_name); return Ok(None); } Ok(Some(DeviceConfigurator { instance, config, physical_device, device_name, available_extensions, used_extensions, queue_family_surface_support, alloc, create_info: vk::DeviceCreateInfo::builder() })) } fn get_name(&self) -> &CStr { self.device_name.as_c_str() } fn get_properties(&self, mut properties: vk::PhysicalDeviceProperties2Builder) -> vk::PhysicalDeviceProperties { unsafe { self.instance.vk().get_physical_device_properties2(self.physical_device, &mut properties) }; properties.properties } fn get_features(&self, mut features: vk::PhysicalDeviceFeatures2Builder) -> vk::PhysicalDeviceFeatures { unsafe { self.instance.vk().get_physical_device_features2(self.physical_device, &mut features) }; features.features } fn filter_sort_queues Option>(&self, func: F) -> Vec { let properties = unsafe { self.instance.vk().get_physical_device_queue_family_properties(self.physical_device) }; let mut families = Vec::with_capacity(properties.len()); for (family, properties) in properties.iter().enumerate() { let surface_supported = self.queue_family_surface_support[family]; if let Some(value) = func(family as u32, properties, surface_supported) { families.push((family as u32, value)); } } families.sort_by(|(a, _), (b, _)| a.cmp(b)); families.into_iter().map(|(_, family)| family).collect() } fn is_extension_supported(&self, name: &CStr) -> bool { self.available_extensions.contains(name) } /// Checks if the extension is supported and if so adds it to the list of used extensions. /// /// Returns true if the extension is supported. fn add_extension(&mut self, name: &CStr) { self.used_extensions.insert(CString::from(name)); } fn allocate(&self, data: T) -> &'b mut T { self.alloc.alloc(data) } fn push_next(&mut self, data: T) { let data = self.alloc.alloc(data); // TODO This looks horrifying do we have a better way? let info = std::mem::replace(&mut self.create_info, vk::DeviceCreateInfo::builder()); self.create_info = info.push_next(data); } fn build(self) -> vk::DeviceCreateInfoBuilder<'b> { let c_extensions = self.alloc.alloc_slice_fill_copy(self.used_extensions.len(), std::ptr::null()); for (index, extension) in self.used_extensions.iter().enumerate() { let c_str = self.alloc.alloc_slice_copy(extension.as_bytes_with_nul()).as_ptr() as *const c_char; c_extensions[index] = c_str; } self.create_info.enabled_extension_names(c_extensions) } } #[derive(Debug)] struct DeviceConfigInfo { rating: f32, has_maintenance4: bool, /// The main queue family. It is guaranteed to support presentation to all surfaces as well as /// graphics, compute and transfer operations. main_queue_family: u32, /// The queue family used for async compute operations. It is guaranteed to support compute and /// transfer operations and must be a different queue family than the main queue family. async_compute_family: Option, /// The queue family used for async transfer operations. It is guaranteed to support transfer /// operations and must be a different queue family than both the main and compute queue family. async_transfer_family: Option, } fn configure_device(device: &mut DeviceConfigurator) -> Result, DeviceCreateError> { // Any device features/properties we need to validate get pushed into this p_next chain let mut features = vk::PhysicalDeviceFeatures2::builder(); let mut properties = vk::PhysicalDeviceProperties2::builder(); let synchronization_2_name = CString::new("VK_KHR_synchronization2").unwrap(); if !device.is_extension_supported(&synchronization_2_name) { log::info!("Physical device {:?} does not support VK_KHR_synchronization2", device.get_name()); return Ok(None); } device.add_extension(&synchronization_2_name); let push_descriptor_name = CString::new("VK_KHR_push_descriptor").unwrap(); if !device.is_extension_supported(&push_descriptor_name) { log::info!("Physical device {:?} does not support VK_KHR_push_descriptor", device.get_name()); return Ok(None); } device.add_extension(&push_descriptor_name); let maintenance_4_name = CString::new("VK_KHR_maintenance4").unwrap(); let mut maintenance4; if !device.is_extension_supported(&maintenance_4_name) { maintenance4 = Some(( vk::PhysicalDeviceMaintenance4Features::builder(), vk::PhysicalDeviceMaintenance4Properties::builder() )); let (f, p) = maintenance4.as_mut().unwrap(); features = features.push_next(f); properties = properties.push_next(p); } else { maintenance4 = None; } let mut timeline_features = vk::PhysicalDeviceTimelineSemaphoreFeatures::builder(); features = features.push_next(&mut timeline_features); let mut timeline_properties = vk::PhysicalDeviceTimelineSemaphoreProperties::builder(); properties = properties.push_next(&mut timeline_properties); let mut synchronization2_features = vk::PhysicalDeviceSynchronization2Features::builder(); features = features.push_next(&mut synchronization2_features); let mut push_descriptor_properties = vk::PhysicalDevicePushDescriptorPropertiesKHR::builder(); properties = properties.push_next(&mut push_descriptor_properties); // Read supported features and properties device.get_features(features); device.get_properties(properties); let timeline_features = timeline_features.build(); let timeline_properties = timeline_properties.build(); let synchronization2_features = synchronization2_features.build(); let push_descriptor_properties = push_descriptor_properties.build(); let maintenance4 = maintenance4.map(|(f, p)| (f.build(), p.build())); // Process the supported features and properties if timeline_features.timeline_semaphore != vk::TRUE { log::info!("Physical device {:?} does not support the timeline semaphore feature", device.get_name()); return Ok(None); } else { device.push_next(vk::PhysicalDeviceTimelineSemaphoreFeatures::builder() .timeline_semaphore(true) ); } if timeline_properties.max_timeline_semaphore_value_difference < u8::MAX as u64 { log::info!("Physical device {:?} max_timeline_semaphore_value_difference is too low {:?}", device.get_name(), timeline_properties.max_timeline_semaphore_value_difference); return Ok(None); } if synchronization2_features.synchronization2 != vk::TRUE { log::info!("Physical device {:?} does not support the synchronization2 feature", device.get_name()); return Ok(None); } else { device.push_next(vk::PhysicalDeviceSynchronization2Features::builder() .synchronization2(true) ); } if push_descriptor_properties.max_push_descriptors < 8 { log::info!("Physical device {:?} max_push_descriptors is too low {:?}", device.get_name(), push_descriptor_properties.max_push_descriptors); return Ok(None); } let has_maintenance4; if let Some((f, p)) = maintenance4.as_ref() { if f.maintenance4 == vk::TRUE { has_maintenance4 = true; device.add_extension(&maintenance_4_name); device.push_next(vk::PhysicalDeviceMaintenance4Features::builder() .maintenance4(true) ); } else { has_maintenance4 = false; } } else { has_maintenance4 = false; } // Calculate queue family assignments let main_families = device.filter_sort_queues(|family, properties, surface_support| { Some(family) }); let main_queue_family; if let Some(family) = main_families.get(0) { main_queue_family = *family; } else { log::info!("Physical device {:?} does not have suitable main queue family", device.get_name()); return Ok(None); } Ok(Some(DeviceConfigInfo { rating: 0.0, has_maintenance4, main_queue_family, async_compute_family: None, async_transfer_family: None })) } ================================================ FILE: core/natives/src/device/mod.rs ================================================ pub mod device; pub mod init; pub mod device_utils; pub mod surface; ================================================ FILE: core/natives/src/device/surface.rs ================================================ use std::fmt::{Debug, Formatter}; use std::ops::{BitAnd, BitOr}; use std::sync::{Arc, Mutex, MutexGuard, Weak}; use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; use ash::prelude::VkResult; use ash::vk; use ash::vk::Flags; use crate::objects::sync::{Semaphore, SemaphoreOp}; use crate::vk::objects::surface::SurfaceProvider; use crate::prelude::*; use crate::vk::objects::image::Image; pub struct DeviceSurface { device: Arc, weak: Weak, #[allow(unused)] // Just here to keep the provider alive surface_provider: Box, surface: vk::SurfaceKHR, /// The current swapchain info. /// /// If both the swapchain mutex and the info mutex must be lock simultaneously (for example during /// creation and destruction) then the info mutex **must always** be lock first to avoid a deadlock. current_swapchain: Mutex, } impl DeviceSurface { pub fn new(device: Arc, surface: Box) -> Arc { Arc::new_cyclic(|weak| Self { device, weak: weak.clone(), surface: surface.get_handle().unwrap(), surface_provider: surface, current_swapchain: Mutex::new(SurfaceSwapchainInfo::new()) }) } pub fn get_surface_present_modes(&self) -> VkResult> { unsafe { self.device.instance.surface_khr().unwrap().get_physical_device_surface_present_modes(self.device.physical_device, self.surface) } } pub fn get_surface_capabilities(&self) -> VkResult { unsafe { self.device.instance.surface_khr().unwrap().get_physical_device_surface_capabilities(self.device.physical_device, self.surface) } } pub fn get_surface_formats(&self) -> VkResult> { unsafe { self.device.instance.surface_khr().unwrap().get_physical_device_surface_formats(self.device.physical_device, self.surface) } } /// Creates a swapchain from a [`SwapchainConfig`]. /// /// On some implementations (for example Wayland) the current extent field of the surface capabilities /// may be 0 which means it cannot be used to determine the desired extent of the swapchain. As /// such calling code should use some platform dependant way to determine the desired extent. /// /// If the current surface capabilities report a max extent of 0 [`SwapchainCreateError::NoExtent`] /// is returned. /// /// If some part of the config is not supported by the surface [`SwapchainCreateError::Unsupported`] /// is returned. pub fn create_swapchain(&self, config: &SwapchainConfig, extent: Vec2u32) -> Result, SwapchainCreateError> { let capabilities = self.get_surface_capabilities()?; let format = self.find_best_format(&config)?; let mut info = vk::SwapchainCreateInfoKHR::builder() .min_image_count(self.find_best_image_count(&capabilities, &config)?) .image_format(format.format) .image_color_space(format.color_space) .image_extent(self.validate_extent(&capabilities, extent)?) .image_array_layers(1) .image_usage(self.find_best_usage_flags(&capabilities, &config)?) .image_sharing_mode(vk::SharingMode::EXCLUSIVE) .pre_transform(self.find_best_transform(&capabilities, &config)?) .composite_alpha(self.find_best_composite_alpha(&capabilities, &config)?) .present_mode(self.find_best_present_mode(&config)?) .clipped(config.clipped); Ok(self.create_swapchain_direct(&mut info)?) } /// Creates a swapchain from a [`ash::vk::SwapchainCreateInfoKHR`]. /// /// The surface and old_swapchain fields will be overwritten by this function. Any other fields /// or entries in the pNext chain will not be validated. pub fn create_swapchain_direct(&self, info: &mut vk::SwapchainCreateInfoKHR) -> VkResult> { let swapchain_khr = self.device.swapchain_khr.as_ref().unwrap(); info.surface = self.surface; let (mut guard, old_swapchain) = self.lock_current_swapchain(); let swapchain_guard: Option> = if let Some(old_swapchain) = &old_swapchain { // We require that the info mutex is always locked before the swapchain mutex so this is safe let swapchain_guard = old_swapchain.swapchain.lock().unwrap(); info.old_swapchain = *swapchain_guard; Some(swapchain_guard) } else { info.old_swapchain = vk::SwapchainKHR::null(); None }; let new_swapchain = unsafe { swapchain_khr.create_swapchain(info, None) }?; drop(swapchain_guard); let images = unsafe { swapchain_khr.get_swapchain_images(new_swapchain) }.map_err(|err| { unsafe { swapchain_khr.destroy_swapchain(new_swapchain, None); } guard.clear_current(); err })?; let format = vk::SurfaceFormatKHR { format: info.image_format, color_space: info.image_color_space, }; let size = Vec2u32::new(info.image_extent.width, info.image_extent.height); let new_swapchain = Arc::new(SurfaceSwapchain::new(self.weak.upgrade().unwrap(), new_swapchain, images.as_slice(), size, format, info.image_usage)); guard.set_current(&new_swapchain); drop(guard); Ok(new_swapchain) } fn find_best_image_count(&self, capabilities: &vk::SurfaceCapabilitiesKHR, _: &SwapchainConfig) -> Result { if capabilities.max_image_count == 0 { Ok(std::cmp::max(capabilities.min_image_count, 3)) } else { Ok(std::cmp::min(capabilities.max_image_count, std::cmp::max(capabilities.min_image_count, 3))) } } fn find_best_format(&self, config: &SwapchainConfig) -> Result { let supported = self.get_surface_formats()?; for format in config.formats.as_ref() { if supported.contains(format) { return Ok(*format); } } Err(SwapchainCreateError::Unsupported) } fn validate_extent(&self, capabilities: &vk::SurfaceCapabilitiesKHR, extent: Vec2u32) -> Result { if capabilities.max_image_extent.width == 0 || capabilities.max_image_extent.height == 0 { return Err(SwapchainCreateError::NoExtent) } if capabilities.max_image_extent.width < extent[0] || capabilities.min_image_extent.width > extent[0] || capabilities.max_image_extent.height < extent[1] || capabilities.min_image_extent.height > extent[1] { return Err(SwapchainCreateError::Unsupported) } Ok(vk::Extent2D{ width: extent[0], height: extent[1] }) } fn find_best_usage_flags(&self, capabilities: &vk::SurfaceCapabilitiesKHR, config: &SwapchainConfig) -> Result { if !capabilities.supported_usage_flags.contains(config.required_usage) { return Err(SwapchainCreateError::Unsupported); } let optional = capabilities.supported_usage_flags.bitand(config.optional_usage); Ok(config.required_usage.bitor(optional)) } fn find_best_present_mode(&self, config: &SwapchainConfig) -> Result { let supported = self.get_surface_present_modes()?; if supported.contains(&vk::PresentModeKHR::MAILBOX) { return Ok(vk::PresentModeKHR::MAILBOX); } if config.allow_tearing && supported.contains(&vk::PresentModeKHR::IMMEDIATE) { return Ok(vk::PresentModeKHR::IMMEDIATE); } Ok(vk::PresentModeKHR::FIFO) } fn find_best_transform(&self, capabilities: &vk::SurfaceCapabilitiesKHR, _: &SwapchainConfig) -> Result { if capabilities.supported_transforms.contains(capabilities.current_transform) { Ok(capabilities.current_transform) } else if capabilities.supported_transforms.contains(vk::SurfaceTransformFlagsKHR::IDENTITY) { Ok(vk::SurfaceTransformFlagsKHR::IDENTITY) } else if capabilities.supported_transforms.contains(vk::SurfaceTransformFlagsKHR::INHERIT) { Ok(vk::SurfaceTransformFlagsKHR::INHERIT) } else { let mut flag = 1u32; loop { let transform = vk::SurfaceTransformFlagsKHR::from_raw(Flags::from(flag)); if capabilities.supported_transforms.contains(transform) { return Ok(transform); } // The vulkan spec requires at least one bit to be set so we should panic if that's not the case flag = flag.checked_shr(1).unwrap(); } } } fn find_best_composite_alpha(&self, capabilities: &vk::SurfaceCapabilitiesKHR, _: &SwapchainConfig) -> Result { if capabilities.supported_composite_alpha.contains(vk::CompositeAlphaFlagsKHR::OPAQUE) { Ok(vk::CompositeAlphaFlagsKHR::OPAQUE) } else if capabilities.supported_composite_alpha.contains(vk::CompositeAlphaFlagsKHR::INHERIT) { Ok(vk::CompositeAlphaFlagsKHR::INHERIT) } else { let mut flag = 1u32; loop { let comp = vk::CompositeAlphaFlagsKHR::from_raw(Flags::from(flag)); if capabilities.supported_composite_alpha.contains(comp) { return Ok(comp) } // The vulkan spec requires at least one bit to be set so we should panic if that's not the case flag = flag.checked_shr(1).unwrap(); } } } /// Locks the current swapchain info. This function **must not** be called from inside the [`SurfaceSwapchain`] /// as it contains code to prevent a deadlock when the current [`SurfaceSwapchain`] is being dropped /// concurrently. fn lock_current_swapchain(&self) -> (MutexGuard, Option>) { loop { let guard = self.current_swapchain.lock().unwrap(); if let Ok(current) = guard.try_upgrade() { return (guard, current); } // The current swapchain is being dropped. We yield and wait for that to be completed drop(guard); std::thread::yield_now(); } } } struct SurfaceSwapchainInfo { current_swapchain: Option<(UUID, Weak)>, } impl SurfaceSwapchainInfo { fn new() -> Self { Self { current_swapchain: None, } } fn try_upgrade(&self) -> Result>, ()> { if let Some((_, weak)) = &self.current_swapchain { if let Some(arc) = weak.upgrade() { Ok(Some(arc)) } else { Err(()) } } else { Ok(None) } } fn is_current(&self, set_id: UUID) -> bool { if let Some((current, _)) = &self.current_swapchain { set_id == *current } else { false } } fn set_current(&mut self, swapchain: &Arc) { self.current_swapchain = Some((swapchain.set_id, Arc::downgrade(&swapchain))) } fn clear_current(&mut self) { self.current_swapchain = None; } } pub struct SwapchainConfig { pub allow_tearing: bool, pub formats: Box<[vk::SurfaceFormatKHR]>, pub required_usage: vk::ImageUsageFlags, pub optional_usage: vk::ImageUsageFlags, pub clipped: bool, } #[derive(Debug)] pub enum SwapchainCreateError { NoExtent, Unsupported, Vulkan(vk::Result), } impl From for SwapchainCreateError { fn from(result: vk::Result) -> Self { Self::Vulkan(result) } } /// Wraps a swapchain of a [`DeviceSurface`] /// /// The swpachain will be destroyed when this struct is dropped. /// /// This struct implements [`ObjectSetProvider`] for access to swapchain images. The swapchain itself /// can only be accessed by calling [`SurfaceSwapchain::get_swapchain`]. /// /// Holds an internal reference to the owning device surface keeping it alive. pub struct SurfaceSwapchain { surface: Arc, set_id: UUID, swapchain: Mutex, acquire_objects: Box<[AcquireObjects]>, acquire_next_index: AtomicUsize, image_objects: Box<[ImageObjects]>, size: Vec2u32, format: vk::SurfaceFormatKHR, usage: vk::ImageUsageFlags, } impl SurfaceSwapchain { fn new(surface: Arc, swapchain: vk::SwapchainKHR, images: &[vk::Image], size: Vec2u32, format: vk::SurfaceFormatKHR, usage: vk::ImageUsageFlags) -> Self { let device = &surface.device; let acquire_objects = images.iter().map(|_| AcquireObjects::new(device)).collect(); let image_objects = images.iter().map(|image| ImageObjects::new(device, Image::new(*image), format.format) ).collect(); Self { surface, set_id: UUID::new(), swapchain: Mutex::new(swapchain), acquire_objects, acquire_next_index: AtomicUsize::new(0), image_objects, size, format, usage } } /// Returns the surface of this swapchain. pub fn get_surface(&self) -> &Arc { &self.surface } /// Returns the handle of the swapchain. /// /// The since the swapchain must be externally synchronized a mutex is returned for the swapchain. pub fn get_swapchain(&self) -> &Mutex { &self.swapchain } /// Returns all swpachain images. pub fn get_images(&self) -> &[ImageObjects] { self.image_objects.as_ref() } /// Returns the size of the images. pub fn get_image_size(&self) -> Vec2u32 { self.size } /// Returns the format of the swapchain images pub fn get_image_format(&self) -> &vk::SurfaceFormatKHR { &self.format } /// Returns the usage flags of the swapchain images pub fn get_image_usage(&self) -> vk::ImageUsageFlags { self.usage } pub fn acquire_next_image(&self, timeout: u64, fence: Option) -> VkResult<(AcquiredImageInfo, bool)> { let acquire = self.acquire_objects.get(self.get_next_acquire()).unwrap(); let (ready_op, acquire_semaphore) = match acquire.wait_and_get(&self.surface.device, timeout) { None => { return Err(vk::Result::TIMEOUT) } Some(objects) => objects }; let swapchain_khr = self.surface.device.swapchain_khr.as_ref().unwrap(); let guard = self.swapchain.lock().unwrap(); let (image_index, suboptimal) = unsafe { swapchain_khr.acquire_next_image(*guard, timeout, acquire_semaphore.get_handle(), fence.unwrap_or(vk::Fence::null())) }?; drop(guard); Ok((AcquiredImageInfo { acquire_semaphore: SemaphoreOp::new_binary(acquire_semaphore), acquire_ready_semaphore: ready_op, image_index, }, suboptimal)) } pub fn get_device(&self) -> &Arc { &self.surface.device } fn get_next_acquire(&self) -> usize { loop { let old = self.acquire_next_index.load(Ordering::SeqCst); let new = (old + 1) % self.acquire_objects.len(); if self.acquire_next_index.compare_exchange(old, new, Ordering::SeqCst, Ordering::SeqCst).is_ok() { return old; } } } } impl Debug for SurfaceSwapchain { fn fmt(&self, _: &mut Formatter<'_>) -> std::fmt::Result { todo!() } } impl Drop for SurfaceSwapchain { fn drop(&mut self) { let device = &self.surface.device; for acquire in self.acquire_objects.iter_mut() { acquire.destroy(device); } for image in self.image_objects.iter_mut() { image.destroy(device); } let mut guard = self.surface.current_swapchain.lock().unwrap(); // We do this inside the guard to propagate potential panics let swapchain_khr = self.surface.device.swapchain_khr.as_ref().unwrap(); let swapchain = self.swapchain.get_mut().unwrap(); unsafe { swapchain_khr.destroy_swapchain(*swapchain, None) }; if guard.is_current(self.set_id) { guard.clear_current(); } } } struct AcquireObjects { ready_semaphore: Semaphore, ready_wait_value: AtomicU64, acquire_semaphore: Semaphore, } impl AcquireObjects { fn new(device: &DeviceFunctions) -> Self { let mut timeline = vk::SemaphoreTypeCreateInfo::builder() .semaphore_type(vk::SemaphoreType::TIMELINE) .initial_value(0); let info = vk::SemaphoreCreateInfo::builder() .push_next(&mut timeline); let ready_semaphore = Semaphore::new(unsafe { device.vk.create_semaphore(&info, None) }.unwrap()); let info = vk::SemaphoreCreateInfo::builder(); let acquire_semaphore = Semaphore::new(unsafe { device.vk.create_semaphore(&info, None) }.unwrap()); Self { ready_semaphore, ready_wait_value: AtomicU64::new(0), acquire_semaphore } } fn wait_and_get(&self, device: &DeviceFunctions, timeout: u64) -> Option<(SemaphoreOp, Semaphore)> { let semaphore = self.ready_semaphore.get_handle(); loop { let value = self.ready_wait_value.load(Ordering::SeqCst); let wait = vk::SemaphoreWaitInfo::builder() .semaphores(std::slice::from_ref(&semaphore)) .values(std::slice::from_ref(&value)); match unsafe { device.timeline_semaphore_khr.wait_semaphores(&wait, timeout) } { Ok(_) => { let next = value + 1; if self.ready_wait_value.compare_exchange(value, next, Ordering::SeqCst, Ordering::SeqCst).is_ok() { return Some((SemaphoreOp::new_timeline(self.ready_semaphore, next), self.acquire_semaphore)); } } Err(vk::Result::TIMEOUT) => { return None; }, Err(err) => { panic!("Error while waiting for semaphore {:?}", err); } } } } fn destroy(&mut self, device: &DeviceFunctions) { unsafe { device.vk.destroy_semaphore(self.acquire_semaphore.get_handle(), None); device.vk.destroy_semaphore(self.ready_semaphore.get_handle(), None); } } } pub struct ImageObjects { image: Image, framebuffer_view: vk::ImageView, present_semaphore: Semaphore, } impl ImageObjects { fn new(device: &DeviceFunctions, image: Image, format: vk::Format) -> Self { let info = vk::ImageViewCreateInfo::builder() .image(image.get_handle()) .view_type(vk::ImageViewType::TYPE_2D) .format(format) .components(vk::ComponentMapping { r: vk::ComponentSwizzle::IDENTITY, g: vk::ComponentSwizzle::IDENTITY, b: vk::ComponentSwizzle::IDENTITY, a: vk::ComponentSwizzle::IDENTITY }) .subresource_range(vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, level_count: 1, base_array_layer: 0, layer_count: vk::REMAINING_ARRAY_LAYERS }); let framebuffer_view = unsafe { device.vk.create_image_view(&info, None) }.unwrap(); let info = vk::SemaphoreCreateInfo::builder(); let present_semaphore = Semaphore::new(unsafe { device.vk.create_semaphore(&info, None) }.unwrap()); Self { image, framebuffer_view, present_semaphore } } pub fn get_image(&self) -> Image { self.image } pub fn get_framebuffer_view(&self) -> vk::ImageView { self.framebuffer_view } pub fn get_present_semaphore(&self) -> Semaphore { self.present_semaphore } fn destroy(&mut self, device: &DeviceFunctions) { unsafe { device.vk.destroy_semaphore(self.present_semaphore.get_handle(), None); device.vk.destroy_image_view(self.framebuffer_view, None); } } } pub struct AcquiredImageInfo { /// Semaphore wait op waiting for the acquire operation to complete. pub acquire_semaphore: SemaphoreOp, /// Semaphore signal op which should be signaled when the acquire semaphore can be used again. pub acquire_ready_semaphore: SemaphoreOp, /// The index of the swapchain image acquired. pub image_index: u32, } ================================================ FILE: core/natives/src/glfw_surface.rs ================================================ use std::ffi::{c_void, CStr, CString}; use std::os::raw::c_char; use std::panic::catch_unwind; use std::process::exit; use ash::vk; use crate::vk::objects::surface::{SurfaceInitError, SurfaceProvider}; #[allow(non_camel_case_types)] pub type PFN_glfwInitVulkanLoader = unsafe extern "C" fn(vk::PFN_vkGetInstanceProcAddr); #[allow(non_camel_case_types)] pub type PFN_glfwGetRequiredInstanceExtensions = unsafe extern "C" fn(*mut u32) -> *const *const c_char; #[allow(non_camel_case_types)] pub type PFN_glfwCreateWindowSurface = unsafe extern "C" fn(vk::Instance, *const c_void, *const vk::AllocationCallbacks, *mut vk::SurfaceKHR) -> vk::Result; pub struct GLFWSurfaceProvider { required_extension: Vec, create_surface_fn: PFN_glfwCreateWindowSurface, glfw_window: *const c_void, surface: Option<(vk::SurfaceKHR, ash::extensions::khr::Surface)>, } impl GLFWSurfaceProvider { pub fn new( window: *const c_void, glfw_get_required_instance_extensions: PFN_glfwGetRequiredInstanceExtensions, glfw_create_window_surface: PFN_glfwCreateWindowSurface, ) -> Self { let mut count = 0u32; let extensions = unsafe { glfw_get_required_instance_extensions(&mut count) }; if extensions.is_null() { log::error!("Extensions returned by glfwGetRequiredInstanceExtensions is null"); panic!(); } let extensions = unsafe { std::slice::from_raw_parts(extensions, count as usize) }; let extensions: Vec<_> = extensions.into_iter().map(|str| { unsafe { CString::from(CStr::from_ptr(*str)) } }).collect(); Self { required_extension: extensions, create_surface_fn: glfw_create_window_surface, glfw_window: window, surface: None } } } impl SurfaceProvider for GLFWSurfaceProvider { fn get_required_instance_extensions(&self) -> Vec { self.required_extension.clone() } fn init(&mut self, entry: &ash::Entry, instance: &ash::Instance) -> Result { let surface_khr = ash::extensions::khr::Surface::new(entry, instance); let mut surface = vk::SurfaceKHR::null(); unsafe { (self.create_surface_fn)(instance.handle(), self.glfw_window, std::ptr::null(), &mut surface) }.result()?; self.surface = Some((surface, surface_khr)); Ok(surface) } fn get_handle(&self) -> Option { self.surface.as_ref().map(|s| s.0) } } // THIS IS NOT CORRECT!!! TODO find a better way unsafe impl Send for GLFWSurfaceProvider { } unsafe impl Sync for GLFWSurfaceProvider { } impl Drop for GLFWSurfaceProvider { fn drop(&mut self) { self.surface.take().map(|s| { unsafe { s.1.destroy_surface(s.0, None) }; }); } } #[no_mangle] unsafe extern "C" fn b4d_pre_init_glfw(func: PFN_glfwInitVulkanLoader) { catch_unwind(|| { let entry = ash::Entry::linked(); func(entry.static_fn().get_instance_proc_addr); }).unwrap_or_else(|_| { log::error!("panic in b4d_pre_init_glfw"); exit(1); }) } #[no_mangle] unsafe extern "C" fn b4d_create_glfw_surface_provider( window: *const c_void, glfw_get_required_instance_extensions: PFN_glfwGetRequiredInstanceExtensions, glfw_create_window_surface: PFN_glfwCreateWindowSurface, ) -> *mut GLFWSurfaceProvider { catch_unwind(|| { Box::leak(Box::new(GLFWSurfaceProvider::new( window, glfw_get_required_instance_extensions, glfw_create_window_surface ))) }).unwrap_or_else(|_| { log::error!("panic in b4d_create_glfw_surface_provider"); exit(1); }) } ================================================ FILE: core/natives/src/instance/debug_messenger.rs ================================================ use std::ffi::CStr; use std::fmt::Debug; use std::panic::{RefUnwindSafe, UnwindSafe}; use ash::vk; pub trait DebugMessengerCallback: Send + Sync + UnwindSafe + RefUnwindSafe + Debug { fn on_message( &self, message_severity: vk::DebugUtilsMessageSeverityFlagsEXT, message_types: vk::DebugUtilsMessageTypeFlagsEXT, message: &CStr, data: &vk::DebugUtilsMessengerCallbackDataEXT, ); } #[derive(Debug)] pub struct RustLogDebugMessenger { } impl RustLogDebugMessenger { pub fn new() -> Self { Self { } } } impl DebugMessengerCallback for RustLogDebugMessenger { fn on_message(&self, message_severity: vk::DebugUtilsMessageSeverityFlagsEXT, _: vk::DebugUtilsMessageTypeFlagsEXT, message: &CStr, _: &vk::DebugUtilsMessengerCallbackDataEXT) { if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::ERROR) { log::error!("{:?}", message); } else if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::WARNING) { log::warn!("{:?}", message); } else if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::INFO) { log::info!("{:?}", message); } else { log::info!("Unknown severity: {:?}", message); } } } ================================================ FILE: core/natives/src/instance/init.rs ================================================ use std::collections::HashSet; use std::ffi::{c_void, CStr, CString}; use std::fmt::{Debug, Formatter}; use std::str::Utf8Error; use std::sync::Arc; use ash::vk; use vk_profiles_rs::vp; use crate::{BUILD_INFO, CRATE_NAME}; use crate::instance::debug_messenger::DebugMessengerCallback; use crate::instance::instance::VulkanVersion; use crate::prelude::*; #[derive(Debug)] pub struct InstanceCreateConfig { application_name: CString, application_version: u32, debug_messengers: Vec, enable_validation: bool, required_extensions: HashSet, require_surface_khr: bool, } impl InstanceCreateConfig { pub fn new(application_name: CString, application_version: u32) -> Self { Self { application_name, application_version, debug_messengers: Vec::new(), enable_validation: false, required_extensions: HashSet::new(), require_surface_khr: false, } } pub fn add_debug_messenger(&mut self, messenger: Box) { self.debug_messengers.push(DebugUtilsMessengerWrapper{ callback: messenger }); } pub fn enable_validation(&mut self) { self.enable_validation = true; } pub fn add_required_extension(&mut self, extension: &CStr) { self.required_extensions.insert(CString::from(extension)); } pub fn require_surface_khr(&mut self) { self.require_surface_khr = true; } } #[derive(Debug)] pub enum InstanceCreateError { Vulkan(vk::Result), ProfileNotSupported, MissingExtension(CString), Utf8Error(Utf8Error), } impl From for InstanceCreateError { fn from(result: vk::Result) -> Self { InstanceCreateError::Vulkan(result) } } impl From for InstanceCreateError { fn from(err: Utf8Error) -> Self { InstanceCreateError::Utf8Error(err) } } pub fn create_instance(config: InstanceCreateConfig) -> Result, InstanceCreateError> { log::info!("Creating vulkan instance with config: {:?}", config); let profile = vp::LunargDesktopPortability2021::profile_properties(); let entry = ash::Entry::linked(); let vp_fn = vk_profiles_rs::VulkanProfiles::linked(); let vulkan_version; if let Some(version) = entry.try_enumerate_instance_version()? { vulkan_version = VulkanVersion::from_raw(version); } else { vulkan_version = VulkanVersion::VK_1_0; } log::info!("Vulkan instance version: {:?}", vulkan_version); log::info!("Using profile {:?} for instance creation", unsafe { CStr::from_ptr(profile.profile_name.as_ptr()) }); if !unsafe { vp_fn.get_instance_profile_support(None, &profile)? } { return Err(InstanceCreateError::ProfileNotSupported) } let mut required_extensions = config.required_extensions; if config.require_surface_khr { required_extensions.insert(CString::from(CStr::from_bytes_with_nul(b"VK_KHR_surface\0").unwrap())); } if !config.debug_messengers.is_empty() { required_extensions.insert(CString::from(CStr::from_bytes_with_nul(b"VK_EXT_debug_utils\0").unwrap())); } let available_extensions: HashSet<_> = entry.enumerate_instance_extension_properties(None)? .into_iter().map(|ext| { CString::from(unsafe { CStr::from_ptr(ext.extension_name.as_ptr()) }) }).collect(); let mut required_extensions_str = Vec::with_capacity(required_extensions.len()); for name in &required_extensions { if available_extensions.contains(name) { required_extensions_str.push(name.as_c_str().as_ptr()) } else { return Err(InstanceCreateError::MissingExtension(name.clone())); } } let required_layers = if config.enable_validation { log::info!("Validation layers enabled"); vec![CStr::from_bytes_with_nul(b"VK_LAYER_KHRONOS_validation\0").unwrap().as_ptr()] } else { log::info!("Validation layers disabled"); Vec::new() }; let max_api_version = VulkanVersion::VK_1_1; let name = CString::new(CRATE_NAME).unwrap(); let application_info = vk::ApplicationInfo::builder() .application_name(config.application_name.as_c_str()) .application_version(config.application_version) .engine_name(&name) .engine_version(vk::make_api_version(0, BUILD_INFO.version_major, BUILD_INFO.version_minor, BUILD_INFO.version_patch)) .api_version(max_api_version.into()); let mut instance_create_info = vk::InstanceCreateInfo::builder() .application_info(&application_info) .enabled_layer_names(required_layers.as_slice()) .enabled_extension_names(required_extensions_str.as_slice()); let debug_messengers = config.debug_messengers.into_boxed_slice(); let mut debug_messenger_create_infos: Vec<_> = debug_messengers.iter().map(|messenger| { vk::DebugUtilsMessengerCreateInfoEXT::builder() .message_severity(vk::DebugUtilsMessageSeverityFlagsEXT::INFO | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING | vk::DebugUtilsMessageSeverityFlagsEXT::ERROR) .message_type(vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE | vk::DebugUtilsMessageTypeFlagsEXT::GENERAL) .pfn_user_callback(Some(debug_utils_messenger_callback_wrapper)) // Sadly this const to mut cast is necessary since the callback provides a mut pointer .user_data(messenger as *const DebugUtilsMessengerWrapper as *mut DebugUtilsMessengerWrapper as *mut c_void) }).collect(); for debug_messenger in debug_messenger_create_infos.iter_mut() { instance_create_info = instance_create_info.push_next(debug_messenger); } let vp_instance_create_info = vp::InstanceCreateInfo::builder() .profile(&profile) .create_info(&instance_create_info) .flags(vp::InstanceCreateFlagBits::MERGE_EXTENSIONS); let instance = unsafe { vp_fn.create_instance(&entry, &vp_instance_create_info, None) }?; let surface_khr = if required_extensions.contains(CStr::from_bytes_with_nul(b"VK_KHR_surface\0").unwrap()) { Some(ash::extensions::khr::Surface::new(&entry, &instance)) } else { None }; let vulkan_version = std::cmp::min(max_api_version, vulkan_version); Ok(InstanceContext::new( vulkan_version, profile, entry, instance, surface_khr, debug_messengers )) } pub struct DebugUtilsMessengerWrapper { callback: Box } impl Debug for DebugUtilsMessengerWrapper { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.callback.fmt(f) } } extern "system" fn debug_utils_messenger_callback_wrapper( message_severity: vk::DebugUtilsMessageSeverityFlagsEXT, message_types: vk::DebugUtilsMessageTypeFlagsEXT, p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT, p_user_data: *mut c_void ) -> vk::Bool32 { std::panic::catch_unwind(|| { if let Some(callback) = unsafe { (p_user_data as *const DebugUtilsMessengerWrapper).as_ref() } { let data = unsafe { p_callback_data.as_ref().unwrap_or_else(|| std::process::abort()) // If this is null something went very wrong }; let message = unsafe { CStr::from_ptr(data.p_message) }; // This is called by c code so we must catch any panics callback.callback.on_message(message_severity, message_types, message, data); } else { log::warn!("Wrapped debug utils messenger was called with null user data!"); } }).unwrap_or_else(|_| { log::error!("Debug utils messenger panicked! Aborting..."); // TODO is there a better way to deal with this? std::process::exit(1); }); return vk::FALSE; } #[cfg(test)] mod tests { use super::*; #[test] fn basic_init() { let config = InstanceCreateConfig::new( CString::from(CStr::from_bytes_with_nul(b"B4DCoreTest\0").unwrap()), 1, ); let instance = create_instance(config).unwrap(); } } ================================================ FILE: core/natives/src/instance/instance.rs ================================================ use core::panic::{UnwindSafe, RefUnwindSafe}; use std::cmp::Ordering; use std::fmt::{Debug, Formatter}; use std::sync::Arc; use ash::vk; use vk_profiles_rs::vp; use crate::instance::init::DebugUtilsMessengerWrapper; use crate::prelude::*; #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] pub struct VulkanVersion(u32); impl VulkanVersion { pub const VK_1_0: VulkanVersion = VulkanVersion(vk::API_VERSION_1_0); pub const VK_1_1: VulkanVersion = VulkanVersion(vk::API_VERSION_1_1); pub const VK_1_2: VulkanVersion = VulkanVersion(vk::API_VERSION_1_2); pub const VK_1_3: VulkanVersion = VulkanVersion(vk::API_VERSION_1_3); pub const fn from_raw(value: u32) -> Self { Self(value) } pub fn new(variant: u32, major: u32, minor: u32, patch: u32) -> Self { Self(vk::make_api_version(variant, major, minor, patch)) } pub const fn get_major(&self) -> u32 { vk::api_version_major(self.0) } pub const fn get_minor(&self) -> u32 { vk::api_version_minor(self.0) } pub const fn get_patch(&self) -> u32 { vk::api_version_patch(self.0) } pub const fn get_raw(&self) -> u32 { self.0 } } impl From for u32 { fn from(version: VulkanVersion) -> Self { version.0 } } impl Debug for VulkanVersion { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(&format!("VulkanVersion([{}] {}.{}.{})", vk::api_version_variant(self.0), vk::api_version_major(self.0), vk::api_version_minor(self.0), vk::api_version_patch(self.0))) } } /// Implementation of the instance context. /// /// Since we need to control drop order most of the fields are ManuallyDrop pub struct InstanceContext { id: NamedUUID, version: VulkanVersion, profile: vp::ProfileProperties, entry: ash::Entry, instance: ash::Instance, surface_khr: Option, _debug_messengers: Box<[DebugUtilsMessengerWrapper]>, } impl InstanceContext { pub fn new( version: VulkanVersion, profile: vp::ProfileProperties, entry: ash::Entry, instance: ash::Instance, surface_khr: Option, debug_messengers: Box<[DebugUtilsMessengerWrapper]> ) -> Arc { Arc::new(Self { id: NamedUUID::with_str("Instance"), version, profile, entry, instance, surface_khr, _debug_messengers: debug_messengers, }) } pub fn get_uuid(&self) -> &NamedUUID { &self.id } pub fn get_entry(&self) -> &ash::Entry { &self.entry } pub fn vk(&self) -> &ash::Instance { &self.instance } pub fn surface_khr(&self) -> Option<&ash::extensions::khr::Surface> { self.surface_khr.as_ref() } pub fn get_version(&self) -> VulkanVersion { self.version } pub fn get_profile(&self) -> &vp::ProfileProperties { &self.profile } } impl Drop for InstanceContext { fn drop(&mut self) { unsafe { self.instance.destroy_instance(None); } } } impl PartialEq for InstanceContext { fn eq(&self, other: &Self) -> bool { self.id.eq(&other.id) } } impl Eq for InstanceContext { } impl PartialOrd for InstanceContext { fn partial_cmp(&self, other: &Self) -> Option { self.id.partial_cmp(&other.id) } } impl Ord for InstanceContext { fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) } } impl Debug for InstanceContext { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.id.fmt(f) } } assert_impl_all!(InstanceContext: Send, Sync, UnwindSafe, RefUnwindSafe); ================================================ FILE: core/natives/src/instance/mod.rs ================================================ pub mod init; pub mod instance; pub mod debug_messenger; ================================================ FILE: core/natives/src/lib.rs ================================================ #[macro_use] extern crate static_assertions; use std::fmt::{Debug, Display, Formatter}; pub mod device; pub mod instance; pub mod objects; pub mod renderer; pub mod vk; pub mod util; pub mod b4d; mod glfw_surface; pub mod window; mod c_api; mod c_log; mod allocator; pub struct BuildInfo { pub version_major: u32, pub version_minor: u32, pub version_patch: u32, pub dev_build: bool, } impl Debug for BuildInfo { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&self, f) } } impl Display for BuildInfo { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if self.dev_build { f.write_fmt(format_args!("{}({}.{}.{}-DEVELOPMENT)", CRATE_NAME, self.version_major, self.version_minor, self.version_patch)) } else { f.write_fmt(format_args!("{}({}.{}.{})", CRATE_NAME, self.version_major, self.version_minor, self.version_patch)) } } } pub const CRATE_NAME: &'static str = "Blaze4D-Core"; pub const BUILD_INFO: BuildInfo = BuildInfo { version_major: 0, version_minor: 1, version_patch: 0, dev_build: option_env!("B4D_RELEASE_BUILD").is_none(), }; pub mod prelude { pub use crate::util::id::UUID; pub use crate::util::id::NamedUUID; pub use crate::instance::instance::InstanceContext; pub use crate::device::device::DeviceFunctions; pub use crate::device::device::Queue; pub use crate::device::device::DeviceContext; pub type Vec2f32 = nalgebra::Vector2; pub type Vec3f32 = nalgebra::Vector3; pub type Vec4f32 = nalgebra::Vector4; pub type Vec2u32 = nalgebra::Vector2; pub type Vec3u32 = nalgebra::Vector3; pub type Vec4u32 = nalgebra::Vector4; pub type Vec2i32 = nalgebra::Vector2; pub type Vec3i32 = nalgebra::Vector3; pub type Vec4i32 = nalgebra::Vector4; pub type Mat2f32 = nalgebra::Matrix2; pub type Mat3f32 = nalgebra::Matrix3; pub type Mat4f32 = nalgebra::Matrix4; } ================================================ FILE: core/natives/src/objects/id.rs ================================================ use std::fmt::{Debug, Formatter}; use std::hash::Hash; use std::ops::Deref; use ash::vk; use ash::vk::Handle; use crate::prelude::*; pub trait ObjectId: Copy + Clone + PartialEq + Eq + PartialOrd + Ord + Hash + Debug { type HandleType: Handle + Copy; fn from_raw(id: UUID) -> Self; fn as_uuid(&self) -> UUID; } macro_rules! declare_object_id { ($name:ident, $handle_type:ty) => { #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct $name(UUID); impl $name { pub fn new() -> Self { Self(UUID::new()) } } impl ObjectId for $name { type HandleType = $handle_type; fn from_raw(id: UUID) -> Self { Self(id) } fn as_uuid(&self) -> UUID { self.0 } } impl Deref for $name { type Target = UUID; fn deref(&self) -> &Self::Target { &self.0 } } impl From<$name> for UUID { fn from(id: $name) -> Self { id.0 } } impl Debug for $name { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!(concat!(stringify!($name), "({:#016X})"), self.0.get_raw())) } } } } declare_object_id!(BufferId, vk::Buffer); declare_object_id!(BufferViewId, vk::BufferView); declare_object_id!(ImageId, vk::Image); declare_object_id!(ImageViewId, vk::ImageView); declare_object_id!(SurfaceId, vk::SurfaceKHR); declare_object_id!(SwapchainId, vk::SwapchainKHR); declare_object_id!(SemaphoreId, vk::Semaphore); ================================================ FILE: core/natives/src/objects/mod.rs ================================================ pub mod id; pub mod sync; mod object_set; pub use object_set::ObjectSetProvider; pub use object_set::ObjectSet; ================================================ FILE: core/natives/src/objects/object_set.rs ================================================ use std::cmp::Ordering; use std::fmt::{Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; use ash::vk::Handle; use super::id::ObjectId; use crate::prelude::*; pub trait ObjectSetProvider: Debug { fn get_id(&self) -> UUID; fn get_handle(&self, id: UUID) -> Option; fn get(&self, id: ID) -> Option where Self: Sized { self.get_handle(id.as_uuid()).map(|handle| ID::HandleType::from_raw(handle)) } } #[derive(Clone)] pub struct ObjectSet(Arc); impl ObjectSet { pub fn new(provider: Arc) -> Self { Self(provider) } pub fn get_provider(&self) -> &Arc { &self.0 } } impl ObjectSetProvider for ObjectSet { fn get_id(&self) -> UUID { self.0.get_id() } fn get_handle(&self, id: UUID) -> Option { self.0.get_handle(id) } } impl PartialEq for ObjectSet { fn eq(&self, other: &Self) -> bool { self.0.get_id().eq(&other.0.get_id()) } } impl Eq for ObjectSet { } impl PartialOrd for ObjectSet { fn partial_cmp(&self, other: &Self) -> Option { self.0.get_id().partial_cmp(&other.0.get_id()) } } impl Ord for ObjectSet { fn cmp(&self, other: &Self) -> Ordering { self.0.get_id().cmp(&other.0.get_id()) } } impl Hash for ObjectSet { fn hash(&self, state: &mut H) { self.0.get_id().hash(state) } } impl Debug for ObjectSet { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { (*self.0).fmt(f) } } ================================================ FILE: core/natives/src/objects/sync.rs ================================================ use std::cmp::Ordering; use std::fmt::{Debug, Formatter}; use std::hash::{Hash, Hasher}; use ash::vk; use ash::vk::Handle; use crate::objects::id::SemaphoreId; #[derive(Copy, Clone)] pub struct Semaphore { id: SemaphoreId, handle: vk::Semaphore, } impl Semaphore { pub fn new(handle: vk::Semaphore) -> Self { Self { id: SemaphoreId::new(), handle, } } pub fn get_id(&self) -> SemaphoreId { self.id } pub fn get_handle(&self) -> vk::Semaphore { self.handle } } impl PartialEq for Semaphore { fn eq(&self, other: &Self) -> bool { self.id.eq(&other.id) } } impl Eq for Semaphore { } impl PartialOrd for Semaphore { fn partial_cmp(&self, other: &Self) -> Option { self.id.partial_cmp(&other.id) } } impl Ord for Semaphore { fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) } } impl Hash for Semaphore { fn hash(&self, state: &mut H) { self.id.hash(state); } } impl Debug for Semaphore { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("vkSemaphore(UUID: {:#016X}, Handle: {:#016X})", self.id.get_raw(), self.handle.as_raw())) } } #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct SemaphoreOp { pub semaphore: Semaphore, pub value: Option, } impl SemaphoreOp { pub fn new_binary(semaphore: Semaphore) -> Self { Self { semaphore, value: None, } } pub fn new_timeline(semaphore: Semaphore, value: u64) -> Self { Self { semaphore, value: Some(value), } } } #[derive(Clone, PartialEq, Eq, Debug)] pub enum SemaphoreOps { None, One(SemaphoreOp), Multiple(Box<[SemaphoreOp]>), } impl SemaphoreOps { pub fn single_binary(semaphore: Semaphore) -> Self { Self::One(SemaphoreOp::new_binary(semaphore)) } pub fn single_timeline(semaphore: Semaphore, value: u64) -> Self { Self::One(SemaphoreOp::new_timeline(semaphore, value)) } pub fn from_option(op: Option) -> Self { match op { None => Self::None, Some(op) => Self::One(op) } } pub fn as_slice(&self) -> &[SemaphoreOp] { match self { SemaphoreOps::None => &[], SemaphoreOps::One(op) => std::slice::from_ref(op), SemaphoreOps::Multiple(ops) => ops.as_ref(), } } } ================================================ FILE: core/natives/src/renderer/emulator/debug_pipeline.rs ================================================ //! Provides a [`EmulatorPipeline`] implementation useful for debugging. use std::collections::HashMap; use std::ffi::CStr; use std::sync::{Arc, Mutex, Weak}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::time::Instant; use ash::vk; use bumpalo::Bump; use bytemuck::{bytes_of, cast_slice, Pod, Zeroable}; use include_bytes_aligned::include_bytes_aligned; use crate::allocator::Allocation; use crate::device::device::Queue; use crate::device::device_utils::create_shader_from_bytes; use crate::prelude::*; use crate::renderer::emulator::EmulatorRenderer; use crate::renderer::emulator::mc_shaders::{McUniform, McUniformData, ShaderDropListener, ShaderId, ShaderListener, VertexFormat, VertexFormatEntry}; use crate::renderer::emulator::pipeline::{DrawTask, EmulatorPipeline, EmulatorPipelinePass, PipelineTask, PooledObjectProvider, SubmitRecorder}; use crate::util::vk::{make_full_rect, make_full_viewport}; pub struct DepthTypeInfo { pub vertex_stride: u32, pub vertex_position_offset: u32, pub vertex_position_format: vk::Format, pub topology: vk::PrimitiveTopology, pub primitive_restart: bool, pub discard: bool, } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub enum ObjectCreateError { Vulkan(vk::Result), Allocation, } impl From for ObjectCreateError { fn from(result: vk::Result) -> Self { Self::Vulkan(result) } } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub enum DebugPipelineMode { Depth, Position, Color, Normal, UV0, UV1, UV2, Textured0, Textured1, Textured2, } /// A [`EmulatorPipeline`] which provides debug information. /// /// The following outputs are supported: /// - Depth: The depth buffer /// - Position: NDC coordinates of the pixel. (Not implemented yet) /// - Color: The color vertex attribute /// - Normal: The normal vertex attribute (Not implemented yet) /// - UV0: The uv0 vertex attribute /// - UV1: The uv1 vertex attribute /// - UV2: The uv2 vertex attribute /// - Textured0: The textured result from uv0 (Not implemented yet) pub struct DebugPipeline { emulator: Arc, weak: Weak, framebuffer_size: Vec2u32, shader_modules: ShaderModules, render_pass: vk::RenderPass, draw_pipeline: DrawPipeline, background_pipeline: BackgroundPipeline, descriptor_pool: vk::DescriptorPool, pipelines: Mutex>, next_index: AtomicUsize, pass_objects: Box<[PassObjects]>, output_views: Box<[vk::ImageView]>, } assert_impl_all!(DebugPipeline: Send, Sync); impl DebugPipeline { pub fn new(emulator: Arc, mode: DebugPipelineMode, framebuffer_size: Vec2u32) -> Result, ObjectCreateError> { let concurrent_passes = 2usize; let depth_format = vk::Format::D32_SFLOAT; let device = emulator.get_device(); let mut shader_modules = ShaderModules::new(device, mode)?; let render_pass = match Self::create_render_pass(&device, depth_format) { Ok(render_pass) => render_pass, Err(err) => { shader_modules.destroy(device); return Err(err); } }; let mut draw_pipeline = match DrawPipeline::new(device) { Ok(pipeline) => pipeline, Err(err) => { unsafe { device.vk().destroy_render_pass(render_pass, None) }; shader_modules.destroy(device); return Err(err); } }; let mut background_pipeline = match BackgroundPipeline::new(device, render_pass, 1, framebuffer_size) { Ok(pipeline) => pipeline, Err(err) => { draw_pipeline.destroy(device); unsafe { device.vk().destroy_render_pass(render_pass, None) }; shader_modules.destroy(device); return Err(err); } }; let descriptor_pool = match Self::create_descriptor_pool(device, concurrent_passes) { Ok(pool) => pool, Err(err) => { background_pipeline.destroy(device); draw_pipeline.destroy(device); unsafe { device.vk().destroy_render_pass(render_pass, None) }; shader_modules.destroy(device); return Err(err); } }; let layouts: Box<[_]> = std::iter::repeat(background_pipeline.descriptor_set_layout).take(concurrent_passes).collect(); let info = vk::DescriptorSetAllocateInfo::builder() .descriptor_pool(descriptor_pool) .set_layouts(&layouts); let descriptor_sets = match unsafe { device.vk().allocate_descriptor_sets(&info) } { Ok(layouts) => layouts, Err(err) => { unsafe { device.vk().destroy_descriptor_pool(descriptor_pool, None) }; background_pipeline.destroy(device); draw_pipeline.destroy(device); unsafe { device.vk().destroy_render_pass(render_pass, None) }; shader_modules.destroy(device); return Err(ObjectCreateError::Vulkan(err)); } }; let mut pass_objects: Vec = Vec::with_capacity(layouts.len()); for descriptor_set in descriptor_sets { let objects = match PassObjects::new(device, framebuffer_size, depth_format, vk::Format::R8G8B8A8_SRGB, render_pass, descriptor_set) { Ok(objects) => objects, Err(err) => { for mut pass_object in pass_objects { pass_object.destroy(device); } unsafe { device.vk().destroy_descriptor_pool(descriptor_pool, None) }; background_pipeline.destroy(device); draw_pipeline.destroy(device); unsafe { device.vk().destroy_render_pass(render_pass, None) }; shader_modules.destroy(device); return Err(err); } }; pass_objects.push(objects); } let pass_objects = pass_objects.into_boxed_slice(); let output_views: Box<_> = if mode == DebugPipelineMode::Depth { pass_objects.iter().map(|obj| obj.depth_sampler_view).collect() } else { pass_objects.iter().map(|obj| obj.output_view).collect() }; Ok(Arc::new_cyclic(|weak| { Self { emulator, weak: weak.clone(), framebuffer_size, shader_modules, render_pass, draw_pipeline, background_pipeline, descriptor_pool, pipelines: Mutex::new(HashMap::new()), next_index: AtomicUsize::new(0), pass_objects, output_views } })) } /// Returns the next index to be used for a pass and increments the internal counter. fn next_index(&self) -> usize { loop { let current = self.next_index.load(Ordering::SeqCst); let next = (current + 1) % self.pass_objects.len(); if self.next_index.compare_exchange(current, next, Ordering::SeqCst, Ordering::SeqCst).is_ok() { return current; } } } /// Returns the pipeline to be used for a specific configuration. If the pipeline doesnt exits /// yet a new one is created. fn get_pipeline(&self, shader: ShaderId, config: &PipelineConfig) -> vk::Pipeline { let mut guard = self.pipelines.lock().unwrap(); let pipelines = guard.get_mut(&shader).unwrap_or_else(|| { log::error!("Called get_pipeline for unregistered shader {:?}", shader); panic!() }); pipelines.get_or_create_pipeline(config, |format| self.create_pipeline(config, format)) } fn create_pipeline(&self, config: &PipelineConfig, vertex_format: &VertexFormat) -> vk::Pipeline { let alloc = Bump::new(); let (shader_stages, input_state) = self.shader_modules.configure_pipeline(vertex_format, &alloc); let viewport = make_full_viewport(self.framebuffer_size); let scissor = make_full_rect(self.framebuffer_size); let viewport_state = vk::PipelineViewportStateCreateInfo::builder() .viewports(std::slice::from_ref(&viewport)) .scissors(std::slice::from_ref(&scissor)); let rasterization_state = vk::PipelineRasterizationStateCreateInfo::builder() .polygon_mode(vk::PolygonMode::FILL) .cull_mode(vk::CullModeFlags::BACK) .front_face(vk::FrontFace::COUNTER_CLOCKWISE) .line_width(1f32); let multisample_state = vk::PipelineMultisampleStateCreateInfo::builder() .rasterization_samples(vk::SampleCountFlags::TYPE_1) .sample_shading_enable(false); let attachment_blend_state = [ vk::PipelineColorBlendAttachmentState::builder() .blend_enable(true) .src_color_blend_factor(vk::BlendFactor::SRC_ALPHA) .dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA) .color_blend_op(vk::BlendOp::ADD) .src_alpha_blend_factor(vk::BlendFactor::ONE) .dst_alpha_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA) .color_blend_op(vk::BlendOp::ADD) .color_write_mask(vk::ColorComponentFlags::RGBA) .build(), ]; let color_blend_state = vk::PipelineColorBlendStateCreateInfo::builder() .logic_op_enable(false) .attachments(&attachment_blend_state); let dynamic_state = vk::PipelineDynamicStateCreateInfo::builder(); let input_assembly_state = vk::PipelineInputAssemblyStateCreateInfo::builder() .topology(config.primitive_topology) .primitive_restart_enable(false); let depth_stencil_state = vk::PipelineDepthStencilStateCreateInfo::builder() .depth_test_enable(config.depth_test_enable) .depth_write_enable(config.depth_write_enable) .depth_compare_op(vk::CompareOp::LESS); let info = vk::GraphicsPipelineCreateInfo::builder() .stages(shader_stages) .vertex_input_state(input_state) .input_assembly_state(&input_assembly_state) .viewport_state(&viewport_state) .rasterization_state(&rasterization_state) .multisample_state(&multisample_state) .depth_stencil_state(&depth_stencil_state) .color_blend_state(&color_blend_state) .dynamic_state(&dynamic_state) .layout(self.draw_pipeline.pipeline_layout) .render_pass(self.render_pass) .subpass(0); let pipeline = *unsafe { self.emulator.get_device().vk().create_graphics_pipelines(vk::PipelineCache::null(), std::slice::from_ref(&info), None) }.unwrap_or_else(|(_, err)| { log::error!("Failed to create graphics pipeline {:?}", err); panic!(); }).get(0).unwrap(); pipeline } fn create_render_pass(device: &DeviceContext, depth_format: vk::Format) -> Result { let attachments = [ vk::AttachmentDescription::builder() .format(depth_format) .samples(vk::SampleCountFlags::TYPE_1) .load_op(vk::AttachmentLoadOp::CLEAR) .store_op(vk::AttachmentStoreOp::STORE) .initial_layout(vk::ImageLayout::UNDEFINED) .final_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) .build(), vk::AttachmentDescription::builder() .format(vk::Format::R8G8B8A8_SRGB) .samples(vk::SampleCountFlags::TYPE_1) .load_op(vk::AttachmentLoadOp::CLEAR) .store_op(vk::AttachmentStoreOp::DONT_CARE) .initial_layout(vk::ImageLayout::UNDEFINED) .final_layout(vk::ImageLayout::GENERAL) .build(), vk::AttachmentDescription::builder() .format(vk::Format::R8G8B8A8_SRGB) .samples(vk::SampleCountFlags::TYPE_1) .load_op(vk::AttachmentLoadOp::DONT_CARE) .store_op(vk::AttachmentStoreOp::STORE) .initial_layout(vk::ImageLayout::UNDEFINED) .final_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) .build() ]; let pass_0_depth = vk::AttachmentReference { attachment: 0, layout: vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; let pass_0_color = [ vk::AttachmentReference { attachment: 1, layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL }, ]; let pass_1_input = [ vk::AttachmentReference { attachment: 1, layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL }, ]; let pass_1_color = [ vk::AttachmentReference { attachment: 2, layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL }, ]; let subpasses = [ vk::SubpassDescription::builder() .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) .color_attachments(&pass_0_color) .depth_stencil_attachment(&pass_0_depth) .build(), vk::SubpassDescription::builder() .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) .input_attachments(&pass_1_input) .color_attachments(&pass_1_color) .build(), ]; let subpass_dependencies = [ vk::SubpassDependency { src_subpass: 0, dst_subpass: 1, src_stage_mask: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, dst_stage_mask: vk::PipelineStageFlags::FRAGMENT_SHADER, src_access_mask: vk::AccessFlags::COLOR_ATTACHMENT_WRITE, dst_access_mask: vk::AccessFlags::SHADER_READ, dependency_flags: vk::DependencyFlags::empty() } ]; let info = vk::RenderPassCreateInfo::builder() .attachments(&attachments) .subpasses(&subpasses) .dependencies(&subpass_dependencies); let render_pass = unsafe { device.vk().create_render_pass(&info, None) }.map_err(|err| { log::error!("vkCreateRenderPass returned {:?} in DebugPipeline::create_render_pass", err); err })?; drop(pass_0_depth); drop(pass_0_color); drop(pass_1_input); drop(pass_1_color); Ok(render_pass) } fn create_descriptor_pool(device: &DeviceContext, concurrent_passes: usize) -> Result { let concurrent_passes = concurrent_passes as u32; let sizes = [ vk::DescriptorPoolSize { ty: vk::DescriptorType::INPUT_ATTACHMENT, descriptor_count: concurrent_passes }, ]; let info = vk::DescriptorPoolCreateInfo::builder() .max_sets(concurrent_passes) .pool_sizes(&sizes); let descriptor_pool = unsafe { device.vk().create_descriptor_pool(&info, None) }.map_err(|err| { log::error!("vkCreateDescriptorPool returned {:?} in DebugPipeline::create_descriptor_pool", err); err })?; Ok(descriptor_pool) } } impl EmulatorPipeline for DebugPipeline { fn start_pass(&self) -> Box { let index = self.next_index(); self.pass_objects[index].wait_and_take(); Box::new(DebugPipelinePass::new(self.weak.upgrade().unwrap(), index)) } fn get_output(&self) -> (Vec2u32, &[vk::ImageView]) { (self.framebuffer_size, &self.output_views) } fn inc_shader_used(&self, shader: ShaderId) { let mut guard = self.pipelines.lock().unwrap(); if let Some(pipelines) = guard.get_mut(&shader) { pipelines.inc_used(); } else { let listener = self.emulator.get_shader(shader).unwrap_or_else(|| { log::error!("Called inc_shader_used for nonexistent shader {:?}", shader); panic!() }).register_drop_listener(&(self.weak.upgrade().unwrap() as Arc)); let shader_obj = self.emulator.get_shader(shader).unwrap(); let vertex_format = shader_obj.get_vertex_format().clone(); let used_uniforms = shader_obj.get_used_uniforms(); let mut pipelines = ShaderPipelines::new(self.emulator.get_device().clone(), vertex_format, used_uniforms, listener); pipelines.inc_used(); guard.insert(shader, pipelines); } } fn dec_shader_used(&self, shader: ShaderId) { let mut guard = self.pipelines.lock().unwrap(); let pipelines = guard.get_mut(&shader).unwrap_or_else(|| { log::error!("Called dec_shader_used for shader which is not registered {:?}", shader); panic!(); }); pipelines.dec_used(); let drop = pipelines.can_drop(); if drop { guard.remove(&shader); } } } impl ShaderDropListener for DebugPipeline { fn on_shader_drop(&self, id: ShaderId) { let mut drop = false; let mut guard = self.pipelines.lock().unwrap(); if let Some(pipeline) = guard.get_mut(&id) { pipeline.mark(); drop = pipeline.can_drop(); } if drop { guard.remove(&id); } } } impl Drop for DebugPipeline { fn drop(&mut self) { let device = self.emulator.get_device(); for objects in self.pass_objects.iter_mut() { objects.destroy(device); } self.pipelines.get_mut().unwrap().clear(); unsafe { device.vk().destroy_descriptor_pool(self.descriptor_pool, None); } self.background_pipeline.destroy(device); self.draw_pipeline.destroy(device); unsafe { device.vk().destroy_render_pass(self.render_pass, None); } self.shader_modules.destroy(device); } } /// The shader modules needed to create vulkan pipelines for the debug pipeline struct ShaderModules { mode: DebugPipelineMode, vertex_module: vk::ShaderModule, null_module: vk::ShaderModule, fragment_module: vk::ShaderModule, texture_module: Option, } impl ShaderModules { fn new(device: &DeviceContext, mode: DebugPipelineMode) -> Result { let null_module = try_create_shader_module(device, DEBUG_NULL_VERTEX_BIN, "null_vertex")?; let fragment_module = try_create_shader_module(device, DEBUG_FRAGMENT_BIN, "fragment").map_err(|err| { unsafe { device.vk().destroy_shader_module(null_module, None) }; err })?; let vertex_module = match mode { DebugPipelineMode::Depth => try_create_shader_module(device, DEBUG_POSITION_VERTEX_BIN, "position_vertex"), DebugPipelineMode::Position => try_create_shader_module(device, DEBUG_POSITION_VERTEX_BIN, "position_vertex"), DebugPipelineMode::Color => try_create_shader_module(device, DEBUG_COLOR_VERTEX_BIN, "color_vertex"), DebugPipelineMode::Normal => { todo!() } DebugPipelineMode::UV0 | DebugPipelineMode::UV1 | DebugPipelineMode::UV2 | DebugPipelineMode::Textured0 | DebugPipelineMode::Textured1 | DebugPipelineMode::Textured2 => try_create_shader_module(device, DEBUG_UV_VERTEX_BIN, "uv_vertex"), }.map_err(|err| { unsafe { device.vk().destroy_shader_module(null_module, None); device.vk().destroy_shader_module(fragment_module, None); } err })?; let texture_module = match mode { DebugPipelineMode::Textured0 => try_create_shader_module(device, TEXTURED_FRAGMENT_BIN, "textured_fragment").map(|val| Some(val)), _ => Ok(None), }.map_err(|err| { unsafe { device.vk().destroy_shader_module(null_module, None); device.vk().destroy_shader_module(fragment_module, None); device.vk().destroy_shader_module(vertex_module, None); } err })?; Ok(Self { mode, vertex_module, null_module, fragment_module, texture_module, }) } fn configure_pipeline<'s, 'a: 's>(&'s self, vertex_format: &VertexFormat, alloc: &'a Bump) -> (&'a [vk::PipelineShaderStageCreateInfo], &'a vk::PipelineVertexInputStateCreateInfo) { let input_bindings: &[_] = alloc.alloc([ vk::VertexInputBindingDescription { binding: 0, stride: vertex_format.stride, input_rate: vk::VertexInputRate::VERTEX } ]); let vertex_module; let input_attributes: &[_]; let vertex_format_supported; if let Some(entry) = self.process_vertex_format(vertex_format) { vertex_format_supported = true; vertex_module = self.vertex_module; input_attributes = alloc.alloc([ vk::VertexInputAttributeDescription { location: 0, binding: 0, format: vertex_format.position.format, offset: vertex_format.position.offset, }, vk::VertexInputAttributeDescription { location: 1, binding: 0, format: entry.format, offset: entry.offset } ]); } else { vertex_format_supported = false; vertex_module = self.null_module; input_attributes = alloc.alloc([ vk::VertexInputAttributeDescription { location: 0, binding: 0, format: vertex_format.position.format, offset: vertex_format.position.offset, }, ]); } let (fragment_module, fragment_specialization) = match (self.mode, vertex_format_supported) { (DebugPipelineMode::Textured0, true) | (DebugPipelineMode::Textured1, true) | (DebugPipelineMode::Textured2, true) => { let data = alloc.alloc(match self.mode { DebugPipelineMode::Textured0 => 0u32, DebugPipelineMode::Textured1 => 1u32, DebugPipelineMode::Textured2 => 2u32, _ => panic!(), }); let entries = alloc.alloc([ vk::SpecializationMapEntry { constant_id: 0, offset: 0, size: 4 } ]); (*self.texture_module.as_ref().unwrap(), alloc.alloc(vk::SpecializationInfo::builder() .map_entries(entries) .data(bytes_of(data)) )) } _ => { (self.fragment_module, alloc.alloc(vk::SpecializationInfo::builder())) } }; let shader_stages: &[_] = alloc.alloc([ vk::PipelineShaderStageCreateInfo::builder() .stage(vk::ShaderStageFlags::VERTEX) .module(vertex_module) .name(SHADER_ENTRY) .build(), vk::PipelineShaderStageCreateInfo::builder() .stage(vk::ShaderStageFlags::FRAGMENT) .module(fragment_module) .name(SHADER_ENTRY) .specialization_info(fragment_specialization) .build(), ]); let input_state: &_ = alloc.alloc(vk::PipelineVertexInputStateCreateInfo::builder() .vertex_binding_descriptions(input_bindings) .vertex_attribute_descriptions(input_attributes) .build() ); (shader_stages, input_state) } fn process_vertex_format<'a>(&self, vertex_format: &'a VertexFormat) -> Option<&'a VertexFormatEntry> { match self.mode { DebugPipelineMode::Depth | DebugPipelineMode::Position => Some(&vertex_format.position), DebugPipelineMode::Color => vertex_format.color.as_ref(), DebugPipelineMode::Normal => vertex_format.normal.as_ref(), DebugPipelineMode::UV0 | DebugPipelineMode::Textured0 => vertex_format.uv0.as_ref(), DebugPipelineMode::UV1 | DebugPipelineMode::Textured1 => vertex_format.uv1.as_ref(), DebugPipelineMode::UV2 | DebugPipelineMode::Textured2 => vertex_format.uv2.as_ref(), } } fn destroy(&mut self, device: &DeviceContext) { unsafe { device.vk().destroy_shader_module(self.vertex_module, None); device.vk().destroy_shader_module(self.null_module, None); device.vk().destroy_shader_module(self.fragment_module, None); if let Some(texture_module) = self.texture_module.take() { device.vk().destroy_shader_module(texture_module, None); } } } } struct DrawPipeline { set0_layout: vk::DescriptorSetLayout, pipeline_layout: vk::PipelineLayout, } impl DrawPipeline { fn new(device: &DeviceContext) -> Result { let bindings = [ vk::DescriptorSetLayoutBinding { binding: 0, descriptor_type: vk::DescriptorType::UNIFORM_BUFFER, descriptor_count: 1, stage_flags: vk::ShaderStageFlags::ALL, p_immutable_samplers: std::ptr::null(), }, vk::DescriptorSetLayoutBinding { binding: 1, descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, descriptor_count: 3, stage_flags: vk::ShaderStageFlags::ALL_GRAPHICS, p_immutable_samplers: std::ptr::null(), }, ]; let info = vk::DescriptorSetLayoutCreateInfo::builder() .flags(vk::DescriptorSetLayoutCreateFlags::PUSH_DESCRIPTOR_KHR) .bindings(&bindings); let set0_layout = unsafe { device.vk().create_descriptor_set_layout(&info, None) }.map_err(|err| { log::error!("vkCreateDescriptorSetLayout returned {:?} in DrawPipeline::new when creating set 0 layout", err); err })?; let push_constant_range = vk::PushConstantRange { stage_flags: vk::ShaderStageFlags::ALL_GRAPHICS, offset: 0, size: std::mem::size_of::() as u32, }; let layouts = [ set0_layout ]; let info = vk::PipelineLayoutCreateInfo::builder() .push_constant_ranges(std::slice::from_ref(&push_constant_range)) .set_layouts(&layouts); let pipeline_layout = unsafe { device.vk().create_pipeline_layout(&info, None) }.map_err(|err| { log::error!("vkCreatePipelineLayout returned {:?} in DrawPipeline::new", err); unsafe { device.vk().destroy_descriptor_set_layout(set0_layout, None) }; err })?; Ok(Self { set0_layout, pipeline_layout }) } fn destroy(&mut self, device: &DeviceContext) { unsafe { device.vk().destroy_pipeline_layout(self.pipeline_layout, None); device.vk().destroy_descriptor_set_layout(self.set0_layout, None); } } } struct BackgroundPipeline { descriptor_set_layout: vk::DescriptorSetLayout, pipeline_layout: vk::PipelineLayout, pipeline: vk::Pipeline, } impl BackgroundPipeline { fn new(device: &DeviceContext, render_pass: vk::RenderPass, subpass: u32, framebuffer_size: Vec2u32) -> Result { let bindings = [ vk::DescriptorSetLayoutBinding { binding: 0, descriptor_type: vk::DescriptorType::INPUT_ATTACHMENT, descriptor_count: 1, stage_flags: vk::ShaderStageFlags::FRAGMENT, p_immutable_samplers: std::ptr::null() }, ]; let info = vk::DescriptorSetLayoutCreateInfo::builder() .bindings(&bindings); let descriptor_set_layout = unsafe { device.vk().create_descriptor_set_layout(&info, None) }.map_err(|err| { log::error!("vkCreateDescriptorSetLayout returned {:?} in BackgroundPipeline::new", err); err })?; let info = vk::PipelineLayoutCreateInfo::builder() .set_layouts(std::slice::from_ref(&descriptor_set_layout)); let pipeline_layout = unsafe { device.vk().create_pipeline_layout(&info, None) }.map_err(|err| { log::error!("vkCreatePipelineLayout returned {:?} in BackgroundPipeline::new", err); unsafe { device.vk().destroy_descriptor_set_layout(descriptor_set_layout, None) }; err })?; let pipeline = Self::create_pipeline(device, pipeline_layout, render_pass, subpass, framebuffer_size).map_err(|err| { unsafe { device.vk().destroy_pipeline_layout(pipeline_layout, None); device.vk().destroy_descriptor_set_layout(descriptor_set_layout, None); } err })?; Ok(Self { descriptor_set_layout, pipeline_layout, pipeline }) } fn destroy(&mut self, device: &DeviceContext) { unsafe { device.vk().destroy_pipeline(self.pipeline, None); device.vk().destroy_pipeline_layout(self.pipeline_layout, None); device.vk().destroy_descriptor_set_layout(self.descriptor_set_layout, None); } } fn create_pipeline(device: &DeviceContext, layout: vk::PipelineLayout, render_pass: vk::RenderPass, subpass: u32, framebuffer_size: Vec2u32) -> Result { let vertex_module = try_create_shader_module(device, BACKGROUND_VERTEX_BIN, "background_vert")?; let fragment_module = try_create_shader_module(device, BACKGROUND_FRAGMENT_BIN, "background_frag").map_err(|err| { unsafe { device.vk().destroy_shader_module(vertex_module, None) }; err })?; let specialization_data = Vec2f32::new(framebuffer_size[0] as f32, framebuffer_size[1] as f32); let specializations = [ vk::SpecializationMapEntry { constant_id: 0, offset: 0, size: 4 }, vk::SpecializationMapEntry { constant_id: 1, offset: 4, size: 4 } ]; let specialization_info = vk::SpecializationInfo::builder() .map_entries(&specializations) .data(cast_slice(specialization_data.data.as_slice())); let shader_stages = [ vk::PipelineShaderStageCreateInfo::builder() .stage(vk::ShaderStageFlags::VERTEX) .module(vertex_module) .name(SHADER_ENTRY) .specialization_info(&specialization_info) .build(), vk::PipelineShaderStageCreateInfo::builder() .stage(vk::ShaderStageFlags::FRAGMENT) .module(fragment_module) .name(SHADER_ENTRY) .build() ]; let input_state = vk::PipelineVertexInputStateCreateInfo::builder(); let input_assembly_state = vk::PipelineInputAssemblyStateCreateInfo::builder() .topology(vk::PrimitiveTopology::TRIANGLE_STRIP) .primitive_restart_enable(false); let depth_stencil_state = vk::PipelineDepthStencilStateCreateInfo::builder() .depth_test_enable(false) .depth_write_enable(false) .depth_bounds_test_enable(false) .stencil_test_enable(false); let viewport = make_full_viewport(framebuffer_size); let scissor = make_full_rect(framebuffer_size); let viewport_state = vk::PipelineViewportStateCreateInfo::builder() .viewports(std::slice::from_ref(&viewport)) .scissors(std::slice::from_ref(&scissor)); let rasterization_state = vk::PipelineRasterizationStateCreateInfo::builder() .polygon_mode(vk::PolygonMode::FILL) .cull_mode(vk::CullModeFlags::NONE) .front_face(vk::FrontFace::CLOCKWISE) .line_width(1f32); let multisample_state = vk::PipelineMultisampleStateCreateInfo::builder() .rasterization_samples(vk::SampleCountFlags::TYPE_1) .sample_shading_enable(false); let attachment_blend_state = [ vk::PipelineColorBlendAttachmentState::builder() .blend_enable(false) .color_write_mask(vk::ColorComponentFlags::RGBA) .build() ]; let color_blend_state = vk::PipelineColorBlendStateCreateInfo::builder() .logic_op_enable(false) .attachments(&attachment_blend_state); let dynamic_state = vk::PipelineDynamicStateCreateInfo::builder(); let info = vk::GraphicsPipelineCreateInfo::builder() .stages(&shader_stages) .vertex_input_state(&input_state) .input_assembly_state(&input_assembly_state) .viewport_state(&viewport_state) .rasterization_state(&rasterization_state) .multisample_state(&multisample_state) .depth_stencil_state(&depth_stencil_state) .color_blend_state(&color_blend_state) .dynamic_state(&dynamic_state) .layout(layout) .render_pass(render_pass) .subpass(subpass); let pipeline = *unsafe { device.vk().create_graphics_pipelines(vk::PipelineCache::null(), std::slice::from_ref(&info), None) }.map_err(|(_, err)| { log::error!("vkCreateGraphicsPipelines returned {:?} in BackgroundPipeline::create_pipeline", err); unsafe { device.vk().destroy_shader_module(vertex_module, None); device.vk().destroy_shader_module(fragment_module, None); } err })?.get(0).unwrap(); unsafe { device.vk().destroy_shader_module(vertex_module, None); device.vk().destroy_shader_module(fragment_module, None); } drop(specialization_info); Ok(pipeline) } } struct PassObjects { ready: AtomicBool, depth_image: vk::Image, depth_framebuffer_view: vk::ImageView, depth_sampler_view: vk::ImageView, pass_image: vk::Image, pass_view: vk::ImageView, output_image: vk::Image, output_view: vk::ImageView, bg_descriptor_set: vk::DescriptorSet, framebuffer: vk::Framebuffer, allocations: Vec, } impl PassObjects { fn new(device: &DeviceContext, framebuffer_size: Vec2u32, depth_format: vk::Format, color_format: vk::Format, render_pass: vk::RenderPass, bg_descriptor_set: vk::DescriptorSet) -> Result { let mut result = PassObjects { ready: AtomicBool::new(true), depth_image: vk::Image::null(), depth_framebuffer_view: vk::ImageView::null(), depth_sampler_view: vk::ImageView::null(), pass_image: vk::Image::null(), pass_view: vk::ImageView::null(), output_image: vk::Image::null(), output_view: vk::ImageView::null(), bg_descriptor_set, framebuffer: vk::Framebuffer::null(), allocations: Vec::with_capacity(3) }; let (depth_image, allocation) = Self::create_image(device, framebuffer_size, depth_format, vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT | vk::ImageUsageFlags::SAMPLED)?; result.depth_image = depth_image; result.allocations.push(allocation); let depth_framebuffer_view = Self::create_image_view(device, depth_image, depth_format, vk::ImageAspectFlags::DEPTH, false).map_err(|err| { result.destroy(device); err })?; result.depth_framebuffer_view = depth_framebuffer_view; let depth_sampler_view = Self::create_image_view(device, depth_image, depth_format, vk::ImageAspectFlags::DEPTH, true).map_err(|err| { result.destroy(device); err })?; result.depth_sampler_view = depth_sampler_view; let (pass_image, allocation) = Self::create_image(device, framebuffer_size, color_format, vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::INPUT_ATTACHMENT).map_err(|err| { result.destroy(device); err })?; result.pass_image = pass_image; result.allocations.push(allocation); let pass_view = Self::create_image_view(device, pass_image, color_format, vk::ImageAspectFlags::COLOR, false).map_err(|err| { result.destroy(device); err })?; result.pass_view = pass_view; let (output_image, allocation) = Self::create_image(device, framebuffer_size, color_format, vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::SAMPLED).map_err(|err| { result.destroy(device); err })?; result.output_image = output_image; result.allocations.push(allocation); let output_view = Self::create_image_view(device, output_image, color_format, vk::ImageAspectFlags::COLOR, false).map_err(|err| { result.destroy(device); err })?; result.output_view = output_view; let framebuffer = Self::create_framebuffer(device, framebuffer_size, depth_framebuffer_view, pass_view, output_view, render_pass).map_err(|err| { result.destroy(device); err })?; result.framebuffer = framebuffer; let info = vk::DescriptorImageInfo::builder() .image_view(pass_view) .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL); let write = vk::WriteDescriptorSet::builder() .dst_set(bg_descriptor_set) .dst_binding(0) .dst_array_element(0) .descriptor_type(vk::DescriptorType::INPUT_ATTACHMENT) .image_info(std::slice::from_ref(&info)); unsafe { device.vk().update_descriptor_sets(std::slice::from_ref(&write), &[]) }; Ok(result) } fn wait_and_take(&self) { let mut start = Instant::now(); loop { if self.ready.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst).is_ok() { return; } std::thread::yield_now(); if start.elapsed().as_millis() > 1000 { log::warn!("Hit 1s timeout waiting for next debug pipeline object"); start = Instant::now(); } } } fn destroy(&mut self, device: &DeviceContext) { unsafe { if self.framebuffer != vk::Framebuffer::null() { device.vk().destroy_framebuffer(self.framebuffer, None); } if self.output_view != vk::ImageView::null() { device.vk().destroy_image_view(self.output_view, None); } if self.output_image != vk::Image::null() { device.vk().destroy_image(self.output_image, None); } if self.pass_view != vk::ImageView::null() { device.vk().destroy_image_view(self.pass_view, None); } if self.pass_image != vk::Image::null() { device.vk().destroy_image(self.pass_image, None); } if self.depth_sampler_view != vk::ImageView::null() { device.vk().destroy_image_view(self.depth_sampler_view, None); } if self.depth_framebuffer_view != vk::ImageView::null() { device.vk().destroy_image_view(self.depth_framebuffer_view, None); } if self.depth_image != vk::Image::null() { device.vk().destroy_image(self.depth_image, None); } device.get_allocator().free_memory_pages(&self.allocations); } } fn create_image(device: &DeviceContext, size: Vec2u32, format: vk::Format, usage: vk::ImageUsageFlags) -> Result<(vk::Image, Allocation), ObjectCreateError> { let info = vk::ImageCreateInfo::builder() .image_type(vk::ImageType::TYPE_2D) .format(format) .extent(vk::Extent3D { width: size[0], height: size[1], depth: 1 }) .mip_levels(1) .array_layers(1) .samples(vk::SampleCountFlags::TYPE_1) .tiling(vk::ImageTiling::OPTIMAL) .usage(usage) .sharing_mode(vk::SharingMode::EXCLUSIVE) .initial_layout(vk::ImageLayout::UNDEFINED); unsafe { device.get_allocator().create_gpu_image(&info, &format_args!("DebugPipelineImage")) }.ok_or(ObjectCreateError::Allocation) } fn create_image_view(device: &DeviceContext, image: vk::Image, format: vk::Format, aspect_mask: vk::ImageAspectFlags, swizzle_r: bool) -> Result { let info = vk::ImageViewCreateInfo::builder() .image(image) .view_type(vk::ImageViewType::TYPE_2D) .format(format); let info = if swizzle_r { info.components(vk::ComponentMapping { r: vk::ComponentSwizzle::R, g: vk::ComponentSwizzle::R, b: vk::ComponentSwizzle::R, a: vk::ComponentSwizzle::ONE }) } else { info.components(vk::ComponentMapping { r: vk::ComponentSwizzle::IDENTITY, g: vk::ComponentSwizzle::IDENTITY, b: vk::ComponentSwizzle::IDENTITY, a: vk::ComponentSwizzle::IDENTITY }) }; let info = info .subresource_range(vk::ImageSubresourceRange { aspect_mask, base_mip_level: 0, level_count: 1, base_array_layer: 0, layer_count: 1 }); let image_view = unsafe { device.vk().create_image_view(&info, None) }.map_err(|err| { log::error!("vkCreateImageView returned {:?} in PassObjects::create_image_view", err); err })?; Ok(image_view) } fn create_framebuffer(device: &DeviceContext, size: Vec2u32, depth_view: vk::ImageView, pass_view: vk::ImageView, output_view: vk::ImageView, render_pass: vk::RenderPass) -> Result { let attachments = [ depth_view, pass_view, output_view ]; let info = vk::FramebufferCreateInfo::builder() .render_pass(render_pass) .attachments(&attachments) .width(size[0]) .height(size[1]) .layers(1); let framebuffer = unsafe { device.vk().create_framebuffer(&info, None) }.map_err(|err| { log::error!("vkCreateFramebuffer returned {:?} in PassObjects::create_framebuffer", err); err })?; Ok(framebuffer) } } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] struct PipelineConfig { primitive_topology: vk::PrimitiveTopology, depth_test_enable: bool, depth_write_enable: bool, } struct ShaderPipelines { device: Arc, vertex_format: VertexFormat, used_uniforms: McUniform, pipelines: HashMap, #[allow(unused)] listener: ShaderListener, used_counter: u32, marked: bool, } impl ShaderPipelines { fn new(device: Arc, vertex_format: VertexFormat, used_uniforms: McUniform, listener: ShaderListener) -> Self { Self { device, vertex_format, used_uniforms, pipelines: HashMap::new(), listener, used_counter: 0, marked: false, } } fn get_or_create_pipeline vk::Pipeline>(&mut self, config: &PipelineConfig, create_fn: T) -> vk::Pipeline { if let Some(pipeline) = self.pipelines.get(config) { *pipeline } else { let pipeline = create_fn(&self.vertex_format); self.pipelines.insert(*config, pipeline); pipeline } } fn inc_used(&mut self) { self.used_counter += 1; } fn dec_used(&mut self) { self.used_counter -= 1; } fn mark(&mut self) { self.marked = true; } fn can_drop(&self) -> bool { self.marked && self.used_counter == 0 } } impl Drop for ShaderPipelines { fn drop(&mut self) { for pipeline in self.pipelines.values() { unsafe { self.device.vk().destroy_pipeline(*pipeline, None); } } } } struct DebugPipelinePass { parent: Arc, index: usize, placeholder_texture: vk::ImageView, placeholder_sampler: vk::Sampler, shader_uniforms: HashMap, command_buffer: Option, current_pipeline: Option<(ShaderId, PipelineConfig)>, current_vertex_buffer: Option, current_index_buffer: Option, } impl DebugPipelinePass { fn new(parent: Arc, index: usize) -> Self { Self { parent, index, placeholder_texture: vk::ImageView::null(), placeholder_sampler: vk::Sampler::null(), shader_uniforms: HashMap::new(), command_buffer: None, current_pipeline: None, current_vertex_buffer: None, current_index_buffer: None } } fn update_uniform(&mut self, shader: ShaderId, data: &McUniformData) { if !self.shader_uniforms.contains_key(&shader) { let uniforms = self.parent.pipelines.lock().unwrap().get(&shader).unwrap().used_uniforms; self.shader_uniforms.insert(shader, UniformStateTracker::new(uniforms, self.placeholder_texture, self.placeholder_sampler)); } let tracker = self.shader_uniforms.get_mut(&shader).unwrap(); tracker.update_uniform(data); } fn update_texture(&mut self, shader: ShaderId, index: u32, view: vk::ImageView, sampler: vk::Sampler) { if !self.shader_uniforms.contains_key(&shader) { let uniforms = self.parent.pipelines.lock().unwrap().get(&shader).unwrap().used_uniforms; self.shader_uniforms.insert(shader, UniformStateTracker::new(uniforms, self.placeholder_texture, self.placeholder_sampler)); } let tracker = self.shader_uniforms.get_mut(&shader).unwrap(); tracker.update_texture(index, view, sampler); } fn draw(&mut self, task: &DrawTask, obj: &mut PooledObjectProvider) { let device = self.parent.emulator.get_device(); let cmd = *self.command_buffer.as_ref().unwrap(); let pipeline_config = PipelineConfig { primitive_topology: task.primitive_topology, depth_test_enable: true, depth_write_enable: task.depth_write_enable }; if self.current_pipeline != Some((task.shader, pipeline_config)) { self.current_pipeline = Some((task.shader, pipeline_config)); let new_pipeline = self.parent.get_pipeline(task.shader, &pipeline_config); unsafe { device.vk().cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, new_pipeline); } } if !self.shader_uniforms.contains_key(&task.shader) { log::warn!("Called draw without any shader uniforms. Using default values!"); let uniforms = self.parent.pipelines.lock().unwrap().get(&task.shader).unwrap().used_uniforms; self.shader_uniforms.insert(task.shader, UniformStateTracker::new(uniforms, self.placeholder_texture, self.placeholder_sampler)); } if let Some(tracker) = self.shader_uniforms.get_mut(&task.shader) { if let Some(push_constants) = tracker.validate_push_constants() { unsafe { device.vk().cmd_push_constants( self.command_buffer.unwrap(), self.parent.draw_pipeline.pipeline_layout, vk::ShaderStageFlags::ALL_GRAPHICS, 0, bytes_of(push_constants) ); } } if let Some(static_uniforms) = tracker.validate_static_uniforms() { let (buffer, offset) = obj.allocate_uniform(bytes_of(static_uniforms)); let buffer_info = vk::DescriptorBufferInfo { buffer, offset, range: std::mem::size_of::() as vk::DeviceSize }; let write = vk::WriteDescriptorSet::builder() .dst_binding(0) .dst_array_element(0) .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) .buffer_info(std::slice::from_ref(&buffer_info)); unsafe { device.push_descriptor_khr().cmd_push_descriptor_set( self.command_buffer.unwrap(), vk::PipelineBindPoint::GRAPHICS, self.parent.draw_pipeline.pipeline_layout, 0, std::slice::from_ref(&write) ); } } if let Some(textures) = tracker.validate_textures() { let image_info0 = vk::DescriptorImageInfo { sampler: textures[0].1, image_view: textures[0].0, image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL }; let image_info1 = vk::DescriptorImageInfo { sampler: textures[1].1, image_view: textures[1].0, image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL }; let image_info2 = vk::DescriptorImageInfo { sampler: textures[2].1, image_view: textures[2].0, image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL }; let writes = [ vk::WriteDescriptorSet::builder() .dst_binding(1) .dst_array_element(0) .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) .image_info(std::slice::from_ref(&image_info0)) .build(), vk::WriteDescriptorSet::builder() .dst_binding(1) .dst_array_element(1) .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) .image_info(std::slice::from_ref(&image_info1)) .build(), vk::WriteDescriptorSet::builder() .dst_binding(1) .dst_array_element(2) .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) .image_info(std::slice::from_ref(&image_info2)) .build(), ]; unsafe { device.push_descriptor_khr().cmd_push_descriptor_set( self.command_buffer.unwrap(), vk::PipelineBindPoint::GRAPHICS, self.parent.draw_pipeline.pipeline_layout, 0, &writes ); } } } if self.current_vertex_buffer != Some(task.vertex_buffer) { unsafe { device.vk().cmd_bind_vertex_buffers( cmd, 0, std::slice::from_ref(&task.vertex_buffer), std::slice::from_ref(&0) ); } self.current_vertex_buffer = Some(task.vertex_buffer); } if self.current_index_buffer != Some(task.index_buffer) { unsafe { device.vk().cmd_bind_index_buffer(cmd, task.index_buffer, 0, task.index_type); } self.current_index_buffer = Some(task.index_buffer); } unsafe { device.vk().cmd_draw_indexed(cmd, task.index_count, 1, task.first_index, task.vertex_offset, 0); } } } impl EmulatorPipelinePass for DebugPipelinePass { fn init(&mut self, _: &Queue, obj: &mut PooledObjectProvider, placeholder_texture: vk::ImageView, placeholder_sampler: vk::Sampler) { self.placeholder_texture = placeholder_texture; self.placeholder_sampler = placeholder_sampler; let cmd = obj.get_begin_command_buffer().unwrap(); self.command_buffer = Some(cmd); let device = self.parent.emulator.get_device(); let clear_values = [ vk::ClearValue { depth_stencil: vk::ClearDepthStencilValue { depth: 1.0, stencil: 0 } }, vk::ClearValue { color: vk::ClearColorValue { float32: [0f32, 0f32, 0f32, 0f32], } }, vk::ClearValue { color: vk::ClearColorValue { float32: [0f32, 0f32, 0f32, 0f32], } } ]; let info = vk::RenderPassBeginInfo::builder() .render_pass(self.parent.render_pass) .framebuffer(self.parent.pass_objects[self.index].framebuffer) .render_area(make_full_rect(self.parent.framebuffer_size)) .clear_values(&clear_values); unsafe { device.vk().cmd_begin_render_pass(cmd, &info, vk::SubpassContents::INLINE); } } fn process_task(&mut self, task: &PipelineTask, obj: &mut PooledObjectProvider) { match task { PipelineTask::UpdateUniform(shader, data) => { self.update_uniform(*shader, data); } PipelineTask::UpdateTexture(shader, index, view, sampler) => { self.update_texture(*shader, *index, *view, *sampler); } PipelineTask::Draw(task) => { self.draw(task, obj); } } } fn record<'a>(&mut self, _: &mut PooledObjectProvider, submits: &mut SubmitRecorder<'a>, alloc: &'a Bump) { let device = self.parent.emulator.get_device(); let cmd = self.command_buffer.take().unwrap(); let bg_descriptor_sets = [self.parent.pass_objects[self.index].bg_descriptor_set]; unsafe { device.vk().cmd_next_subpass(cmd, vk::SubpassContents::INLINE); device.vk().cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, self.parent.background_pipeline.pipeline); device.vk().cmd_bind_descriptor_sets(cmd, vk::PipelineBindPoint::GRAPHICS, self.parent.background_pipeline.pipeline_layout, 0, &bg_descriptor_sets, &[]); device.vk().cmd_draw(cmd, 4, 1, 0, 0); } let image_barrier = [ vk::ImageMemoryBarrier2::builder() .src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) .src_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) .dst_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) .dst_access_mask(vk::AccessFlags2::MEMORY_READ) .old_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) .src_queue_family_index(0) .dst_queue_family_index(0) .image(self.parent.pass_objects[self.index].depth_image) .subresource_range(vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::DEPTH, base_mip_level: 0, level_count: 1, base_array_layer: 0, layer_count: 1 }) .build(), vk::ImageMemoryBarrier2::builder() .src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) .src_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) .dst_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS) .dst_access_mask(vk::AccessFlags2::MEMORY_READ) .old_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) .src_queue_family_index(0) .dst_queue_family_index(0) .image(self.parent.pass_objects[self.index].output_image) .subresource_range(vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, level_count: 1, base_array_layer: 0, layer_count: 1 }) .build(), ]; let info = vk::DependencyInfo::builder() .image_memory_barriers(&image_barrier); unsafe { device.vk().cmd_end_render_pass(cmd); device.synchronization_2_khr().cmd_pipeline_barrier2(cmd, &info); device.vk().end_command_buffer(cmd).unwrap(); } let command_buffer_info = alloc.alloc(vk::CommandBufferSubmitInfo::builder() .command_buffer(cmd) ); submits.push(vk::SubmitInfo2::builder() .command_buffer_infos(std::slice::from_ref(command_buffer_info)) ); } fn get_output_index(&self) -> usize { self.index } fn get_internal_fences(&self, _: &mut Vec) { todo!() } } impl Drop for DebugPipelinePass { fn drop(&mut self) { self.parent.pass_objects[self.index].ready.store(true, Ordering::SeqCst); } } struct UniformStateTracker { used_uniforms: McUniform, push_constants_dirty: bool, static_uniforms_dirty: bool, textures_dirty: bool, push_constant_cache: PushConstants, static_uniform_cache: StaticUniforms, textures: [(vk::ImageView, vk::Sampler); 3], } impl UniformStateTracker { fn new(used_uniforms: McUniform, initial_texture: vk::ImageView, initial_sampler: vk::Sampler) -> Self { Self { used_uniforms, push_constants_dirty: true, static_uniforms_dirty: true, textures_dirty: true, push_constant_cache: PushConstants { model_view_matrix: Mat4f32::identity(), chunk_offset: Vec3f32::zeros(), _padding0: Default::default(), }, static_uniform_cache: StaticUniforms { projection_matrix: Mat4f32::identity(), screen_size: Vec2f32::zeros(), _padding0: Default::default(), fog_color: Vec4f32::zeros(), fog_range_and_game_time: Vec3f32::zeros(), _padding1: Default::default(), fog_shape: 0, _padding2: Default::default(), }, textures: [(initial_texture, initial_sampler); 3], } } fn update_uniform(&mut self, data: &McUniformData) { match data { McUniformData::ModelViewMatrix(mat) => { if self.used_uniforms.contains(&McUniform::MODEL_VIEW_MATRIX) { self.push_constant_cache.model_view_matrix = *mat; self.push_constants_dirty = true; } } McUniformData::ProjectionMatrix(mat) => { if self.used_uniforms.contains(&McUniform::PROJECTION_MATRIX) { self.static_uniform_cache.projection_matrix = *mat; self.static_uniforms_dirty = true; } } McUniformData::InverseViewRotationMatrix(_) => {} McUniformData::TextureMatrix(_) => {} McUniformData::ScreenSize(size) => { if self.used_uniforms.contains(&McUniform::SCREEN_SIZE) { self.static_uniform_cache.screen_size = *size; self.static_uniforms_dirty = true; } } McUniformData::ColorModulator(_) => {} McUniformData::Light0Direction(_) => {} McUniformData::Light1Direction(_) => {} McUniformData::FogStart(start) => { if self.used_uniforms.contains(&McUniform::FOG_START) { self.static_uniform_cache.fog_range_and_game_time[0] = *start; self.static_uniforms_dirty = true; } } McUniformData::FogEnd(end) => { if self.used_uniforms.contains(&McUniform::FOG_END) { self.static_uniform_cache.fog_range_and_game_time[1] = *end; self.static_uniforms_dirty = true; } } McUniformData::FogColor(color) => { if self.used_uniforms.contains(&McUniform::FOG_COLOR) { self.static_uniform_cache.fog_color = *color; self.static_uniforms_dirty = true; } } McUniformData::FogShape(shape) => { if self.used_uniforms.contains(&McUniform::FOG_SHAPE) { self.static_uniform_cache.fog_shape = *shape; self.static_uniforms_dirty = true; } } McUniformData::LineWidth(_) => {} McUniformData::GameTime(time) => { if self.used_uniforms.contains(&McUniform::GAME_TIME) { self.static_uniform_cache.fog_range_and_game_time[2] = *time; self.static_uniforms_dirty = true; } } McUniformData::ChunkOffset(offset) => { if self.used_uniforms.contains(&McUniform::CHUNK_OFFSET) { self.push_constant_cache.chunk_offset = *offset; self.push_constants_dirty = true; } } } } fn update_texture(&mut self, index: u32, view: vk::ImageView, sampler: vk::Sampler) { match index { 0 => { self.textures[0] = (view, sampler); self.textures_dirty = true; }, 1 => { self.textures[1] = (view, sampler); self.textures_dirty = true; }, 2 => { self.textures[2] = (view, sampler); self.textures_dirty = true; }, _ => log::warn!("Called updated texture on index {:?} which is out of bounds", index), } } fn validate_push_constants(&mut self) -> Option<&PushConstants> { if self.push_constants_dirty { self.push_constants_dirty = false; Some(&self.push_constant_cache) } else { None } } fn validate_static_uniforms(&mut self) -> Option<&StaticUniforms> { if self.static_uniforms_dirty { self.static_uniforms_dirty = false; Some(&self.static_uniform_cache) } else { None } } fn validate_textures(&mut self) -> Option<&[(vk::ImageView, vk::Sampler); 3]> { if self.textures_dirty { self.textures_dirty = false; Some(&self.textures) } else { None } } } #[repr(C)] #[derive(Copy, Clone, Default)] struct PushConstants { #[allow(unused)] model_view_matrix: Mat4f32, #[allow(unused)] chunk_offset: Vec3f32, _padding0: [u8; 4], } const_assert_eq!(std::mem::size_of::(), 80); const_assert_eq!(std::mem::size_of::() % 16, 0); unsafe impl Zeroable for PushConstants {} unsafe impl Pod for PushConstants {} #[repr(C)] #[derive(Copy, Clone, Default)] struct StaticUniforms { #[allow(unused)] projection_matrix: Mat4f32, #[allow(unused)] screen_size: Vec2f32, _padding0: [u8; 8], #[allow(unused)] fog_color: Vec4f32, #[allow(unused)] fog_range_and_game_time: Vec3f32, _padding1: [u8; 4], #[allow(unused)] fog_shape: u32, _padding2: [u8; 12], } const_assert_eq!(std::mem::size_of::(), 128); const_assert_eq!(std::mem::size_of::() % 16, 0); unsafe impl Zeroable for StaticUniforms {} unsafe impl Pod for StaticUniforms {} fn try_create_shader_module(device: &DeviceContext, data: &[u8], name: &str) -> Result { unsafe { create_shader_from_bytes(device.get_functions(), data) }.map_err(|err| { log::error!("vkCreateShaderModule returned {:?} when creating module {:?}", err, name); err }) } const SHADER_ENTRY: &'static CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"main\0") }; // GOD I LOVE RUSTS FFI API IT IS SO NICE AND DEFINITELY NOT STUPID WITH WHICH FUNCTIONS ARE CONST AND WHICH AREN'T static DEBUG_POSITION_VERTEX_BIN: &'static [u8] = include_bytes_aligned!(4, concat!(env!("B4D_RESOURCE_DIR"), "emulator/debug/position_vert.spv")); static DEBUG_COLOR_VERTEX_BIN: &'static [u8] = include_bytes_aligned!(4, concat!(env!("B4D_RESOURCE_DIR"), "emulator/debug/color_vert.spv")); static DEBUG_UV_VERTEX_BIN: &'static [u8] = include_bytes_aligned!(4, concat!(env!("B4D_RESOURCE_DIR"), "emulator/debug/uv_vert.spv")); static DEBUG_NULL_VERTEX_BIN: &'static [u8] = include_bytes_aligned!(4, concat!(env!("B4D_RESOURCE_DIR"), "emulator/debug/null_vert.spv")); static DEBUG_FRAGMENT_BIN: &'static [u8] = include_bytes_aligned!(4, concat!(env!("B4D_RESOURCE_DIR"), "emulator/debug/debug_frag.spv")); static TEXTURED_FRAGMENT_BIN: &'static [u8] = include_bytes_aligned!(4, concat!(env!("B4D_RESOURCE_DIR"), "emulator/debug/textured_frag.spv")); static BACKGROUND_VERTEX_BIN: &'static [u8] = include_bytes_aligned!(4, concat!(env!("B4D_RESOURCE_DIR"), "emulator/debug/background_vert.spv")); static BACKGROUND_FRAGMENT_BIN: &'static [u8] = include_bytes_aligned!(4, concat!(env!("B4D_RESOURCE_DIR"), "emulator/debug/background_frag.spv")); ================================================ FILE: core/natives/src/renderer/emulator/descriptors.rs ================================================ use std::ptr::NonNull; use std::sync::Arc; use ash::vk; use crate::allocator::{Allocation, HostAccess}; use crate::prelude::*; pub(super) struct DescriptorPool { device: Arc, uniform_buffer_pool: UniformBufferPool, } impl DescriptorPool { pub(super) fn new(device: Arc) -> Self { let uniform_buffer_pool = UniformBufferPool::new(&device); Self { device, uniform_buffer_pool, } } pub(super) fn allocate_uniform(&mut self, data: &[u8]) -> (vk::Buffer, vk::DeviceSize) { self.uniform_buffer_pool.allocate_write(data) } } impl Drop for DescriptorPool { fn drop(&mut self) { self.uniform_buffer_pool.destroy(&self.device); } } struct UniformBufferPool { buffer_allocation: Allocation, buffer: vk::Buffer, buffer_size: usize, current_offset: usize, mapped_ptr: NonNull, } impl UniformBufferPool { fn new(device: &DeviceContext) -> Self { let target_size = 2usize.pow(25); // ~32MB let info = vk::BufferCreateInfo::builder() .size(target_size as vk::DeviceSize) .usage(vk::BufferUsageFlags::UNIFORM_BUFFER) .sharing_mode(vk::SharingMode::EXCLUSIVE); let (buffer, buffer_allocation, ptr) = unsafe { device.get_allocator().create_buffer(&info, HostAccess::Random, &format_args!("UniformBufferPool")) }.unwrap(); Self { buffer_allocation, buffer, buffer_size: target_size, current_offset: 0, mapped_ptr: ptr.unwrap(), } } fn allocate_write(&mut self, data: &[u8]) -> (vk::Buffer, vk::DeviceSize) { // We just allocate a new slot hoping that it isn't in use anymore. This is not that dangerous right now since we have a 32MB buffer which equates to roughly 100k slots // but it for sure can't become a permanent solution. let src = data; if src.len() > 1024 { // Just a sanity check all of our uniforms currently are < 256 panic!("Wtf are you doing???"); } // We align to 256bytes because that was the highest in the gpuinfo database (Yes this is entire module is very much a TODO) let add = 256 - (self.current_offset % 256); let mut base_offset = self.current_offset + add; if base_offset + src.len() > self.buffer_size { base_offset = 0; } let base_offset = base_offset; self.current_offset = base_offset + src.len(); let dst = unsafe { std::slice::from_raw_parts_mut(self.mapped_ptr.as_ptr().offset(base_offset as isize), src.len()) }; dst.copy_from_slice(src); (self.buffer, base_offset as vk::DeviceSize) } fn destroy(&mut self, device: &DeviceContext) { unsafe { device.get_allocator().destroy_buffer(self.buffer, self.buffer_allocation) }; } } unsafe impl Send for UniformBufferPool { } ================================================ FILE: core/natives/src/renderer/emulator/global_objects.rs ================================================ use std::cmp::Ordering; use std::collections::HashMap; use std::hash::{Hash, Hasher}; use std::sync::{Arc, Mutex, Weak}; use std::sync::atomic::AtomicU64; use ash::vk; use crate::allocator::Allocation; use crate::define_uuid_type; use crate::renderer::emulator::{MeshData, PassId}; use crate::prelude::*; use crate::renderer::emulator::share::Share; use crate::renderer::emulator::worker::{GlobalImageClear, GlobalImageWrite, GlobalMeshWrite, WorkerTask}; use crate::util::alloc::next_aligned; use crate::util::format::Format; define_uuid_type!(pub, GlobalMeshId); #[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Debug)] pub enum GlobalObjectCreateError { Vulkan(vk::Result), Allocation, } impl From for GlobalObjectCreateError { fn from(err: vk::Result) -> Self { GlobalObjectCreateError::Vulkan(err) } } pub struct GlobalMesh { share: Arc, id: GlobalMeshId, last_used_pass: AtomicU64, buffer: vk::Buffer, allocation: Allocation, buffer_size: vk::DeviceSize, draw_info: GlobalMeshDrawInfo, } impl GlobalMesh { pub(super) fn new(share: Arc, data: &MeshData) -> Result, GlobalObjectCreateError> { let index_offset = next_aligned(data.vertex_data.len() as vk::DeviceSize, data.get_index_size() as vk::DeviceSize); let required_size = index_offset + (data.index_data.len() as vk::DeviceSize); let (buffer, allocation) = Self::create_buffer(share.get_device(), required_size)?; let (staging, staging_allocation) = share.get_staging_pool().lock().unwrap_or_else(|_| { log::error!("Poisoned staging memory mutex in GlobalMesh::new"); panic!() }).allocate(required_size, 1); unsafe { let dst = std::slice::from_raw_parts_mut(staging.mapped.as_ptr(), required_size as usize); dst[0..data.vertex_data.len()].copy_from_slice(data.vertex_data); dst[(index_offset as usize)..].copy_from_slice(data.index_data); } let draw_info = GlobalMeshDrawInfo { buffer, first_index: (index_offset / (data.get_index_size() as vk::DeviceSize)) as u32, index_type: data.index_type, index_count: data.index_count, primitive_topology: data.primitive_topology }; let mesh = Arc::new(GlobalMesh { share, id: GlobalMeshId::new(), last_used_pass: AtomicU64::new(0), buffer, allocation, buffer_size: required_size, draw_info }); mesh.share.push_task(WorkerTask::WriteGlobalMesh(GlobalMeshWrite { after_pass: PassId::from_raw(0), staging_allocation, staging_range: (staging.offset, required_size), staging_buffer: staging.buffer, dst_mesh: mesh.clone(), regions: Box::new([vk::BufferCopy { src_offset: staging.offset, dst_offset: 0, size: required_size }]) }, true)); Ok(mesh) } pub(super) fn update_used_in(&self, pass: PassId) { let pass = pass.get_raw(); loop { let val = self.last_used_pass.load(std::sync::atomic::Ordering::Acquire); if val >= pass { return; } if self.last_used_pass.compare_exchange(val, pass, std::sync::atomic::Ordering::SeqCst, std::sync::atomic::Ordering::SeqCst).is_ok() { return; } } } pub(super) fn get_buffer_handle(&self) -> vk::Buffer { self.buffer } pub(super) fn get_draw_info(&self) -> &GlobalMeshDrawInfo { &self.draw_info } fn create_buffer(device: &DeviceContext, size: vk::DeviceSize) -> Result<(vk::Buffer, Allocation), GlobalObjectCreateError> { let info = vk::BufferCreateInfo::builder() .size(size) .usage(vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::VERTEX_BUFFER | vk::BufferUsageFlags::INDEX_BUFFER) .sharing_mode(vk::SharingMode::EXCLUSIVE); unsafe { device.get_allocator().create_gpu_buffer(&info, &format_args!("GlobalBuffer")) }.ok_or(GlobalObjectCreateError::Allocation) } } impl PartialEq for GlobalMesh { fn eq(&self, other: &Self) -> bool { self.id.eq(&other.id) } } impl Eq for GlobalMesh { } impl PartialOrd for GlobalMesh { fn partial_cmp(&self, other: &Self) -> Option { self.id.partial_cmp(&other.id) } } impl Ord for GlobalMesh { fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) } } impl Hash for GlobalMesh { fn hash(&self, state: &mut H) { self.id.hash(state) } } impl Drop for GlobalMesh { fn drop(&mut self) { unsafe { self.share.get_device().get_allocator().destroy_buffer(self.buffer, self.allocation) } } } pub(super) struct GlobalMeshDrawInfo { pub(super) buffer: vk::Buffer, pub(super) first_index: u32, pub(super) index_count: u32, pub(super) index_type: vk::IndexType, pub(super) primitive_topology: vk::PrimitiveTopology, } pub struct ImageData<'a> { /// The image data pub data: &'a [u8], /// The stride between 2 rows of image data in texels. If 0 the data is assumed to be tightly packed. pub row_stride: u32, /// The offset of the upload region in the image. pub offset: Vec2u32, /// The size of the upload region in the image. pub extent: Vec2u32, } impl<'a> ImageData<'a> { pub fn new_full(data: &'a [u8], size: Vec2u32) -> Self { Self { data, row_stride: 0, offset: Vec2u32::new(0, 0), extent: size, } } pub fn new_full_with_stride(data: &'a [u8], row_stride: u32, size: Vec2u32) -> Self { Self { data, row_stride, offset: Vec2u32::new(0, 0), extent: size, } } pub fn new_extent(data: &'a [u8], offset: Vec2u32, extent: Vec2u32) -> Self { Self { data, row_stride: 0, offset, extent } } pub fn new_extent_with_stride(data: &'a [u8], row_stride: u32, offset: Vec2u32, extent: Vec2u32) -> Self { Self { data, row_stride, offset, extent } } } define_uuid_type!(pub, GlobalImageId); pub struct GlobalImage { weak: Weak, share: Arc, id: GlobalImageId, last_used_pass: AtomicU64, image: vk::Image, sampler_view: vk::ImageView, allocation: Allocation, size: Vec2u32, mip_levels: u32, sampler_database: Mutex>, } impl GlobalImage { pub(super) fn new(share: Arc, size: Vec2u32, mip_levels: u32, format: &'static Format) -> Result, GlobalObjectCreateError> { let (image, allocation, sampler_view) = Self::create_image(share.get_device(), format.into(), size, mip_levels)?; let image = Arc::new_cyclic(|weak| GlobalImage { weak: weak.clone(), share, id: GlobalImageId::new(), last_used_pass: AtomicU64::new(0), image, sampler_view, allocation, size, mip_levels, sampler_database: Mutex::new(HashMap::new()) }); image.share.push_task(WorkerTask::ClearGlobalImage(GlobalImageClear { after_pass: PassId::from_raw(0), clear_value: format.get_clear_color_type().unwrap().make_zero_clear(), dst_image: image.clone() }, true)); Ok(image) } pub(super) fn update_used_in(&self, pass: PassId) { let pass = pass.get_raw(); loop { let val = self.last_used_pass.load(std::sync::atomic::Ordering::Acquire); if val >= pass { return; } if self.last_used_pass.compare_exchange(val, pass, std::sync::atomic::Ordering::SeqCst, std::sync::atomic::Ordering::SeqCst).is_ok() { return; } } } pub fn get_id(&self) -> GlobalImageId { self.id } pub fn get_size(&self) -> Vec2u32 { self.size } pub fn update_regions(&self, regions: &[ImageData]) { if regions.is_empty() { return; } let required_memory = regions.iter().map(|r| r.data.len()).sum::() as u64; let (staging, allocation) = self.share.get_staging_pool().lock().unwrap().allocate(required_memory as u64, 1); let mut copies = Vec::with_capacity(regions.len()); let mut current_offset = 0; for region in regions { copies.push(vk::BufferImageCopy { buffer_offset: staging.offset + current_offset, buffer_row_length: region.row_stride, buffer_image_height: 0, image_subresource: vk::ImageSubresourceLayers { aspect_mask: vk::ImageAspectFlags::COLOR, mip_level: 0, base_array_layer: 0, layer_count: 1 }, image_offset: vk::Offset3D { x: region.offset[0] as i32, y: region.offset[1] as i32, z: 0 }, image_extent: vk::Extent3D { width: region.extent[0], height: region.extent[1], depth: 1 } }); unsafe { let mapped = std::slice::from_raw_parts_mut(staging.mapped.as_ptr().offset(current_offset as isize), region.data.len()); mapped.copy_from_slice(region.data); } current_offset += region.data.len() as u64; } self.share.push_task(WorkerTask::WriteGlobalImage(GlobalImageWrite { after_pass: PassId::from_raw(self.last_used_pass.load(std::sync::atomic::Ordering::Acquire)), staging_allocation: allocation, staging_range: (staging.offset, required_memory), staging_buffer: staging.buffer, dst_image: self.weak.upgrade().unwrap(), regions: copies.into_boxed_slice() })); } pub(super) fn get_image_handle(&self) -> vk::Image { self.image } pub(super) fn get_mip_levels(&self) -> u32 { self.mip_levels } pub(super) fn get_sampler_view(&self) -> vk::ImageView { self.sampler_view } pub(super) fn get_sampler(&self, sampler_info: &SamplerInfo) -> vk::Sampler { let mut guard = self.sampler_database.lock().unwrap(); if let Some(sampler) = guard.get(sampler_info) { *sampler } else { let info = vk::SamplerCreateInfo::builder() .mag_filter(sampler_info.mag_filter) .min_filter(sampler_info.min_filter) .mipmap_mode(sampler_info.mipmap_mode) .address_mode_u(sampler_info.address_mode_u) .address_mode_v(sampler_info.address_mode_v) .address_mode_w(vk::SamplerAddressMode::REPEAT) .mip_lod_bias(0f32) .anisotropy_enable(sampler_info.anisotropy_enable) .max_anisotropy(0f32) .compare_enable(false) .min_lod(0f32) .max_lod(vk::LOD_CLAMP_NONE) .unnormalized_coordinates(false); let sampler = unsafe { self.share.get_device().vk().create_sampler(&info, None) }.unwrap_or_else(|err| { log::error!("vkCreateSampler returned {:?} in GlobalImage::get_sampler", err); panic!() }); guard.insert(*sampler_info, sampler); sampler } } fn create_image(device: &DeviceContext, format: vk::Format, size: Vec2u32, mip_levels: u32) -> Result<(vk::Image, Allocation, vk::ImageView), GlobalObjectCreateError> { let info = vk::ImageCreateInfo::builder() .image_type(vk::ImageType::TYPE_2D) .format(format) .extent(vk::Extent3D { width: size[0], height: size[1], depth: 1 }) .mip_levels(mip_levels) .array_layers(1) .samples(vk::SampleCountFlags::TYPE_1) .tiling(vk::ImageTiling::OPTIMAL) .usage(vk::ImageUsageFlags::TRANSFER_SRC | vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED) .sharing_mode(vk::SharingMode::EXCLUSIVE) .initial_layout(vk::ImageLayout::UNDEFINED); let (image, allocation) = unsafe { device.get_allocator().create_gpu_image(&info, &format_args!("GlobalImage")) }.ok_or(GlobalObjectCreateError::Allocation)?; let info = vk::ImageViewCreateInfo::builder() .image(image) .view_type(vk::ImageViewType::TYPE_2D) .format(format) .components(vk::ComponentMapping { r: vk::ComponentSwizzle::IDENTITY, g: vk::ComponentSwizzle::IDENTITY, b: vk::ComponentSwizzle::IDENTITY, a: vk::ComponentSwizzle::IDENTITY }) .subresource_range(vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, level_count: mip_levels, base_array_layer: 0, layer_count: 1 }); let sampler_view = match unsafe { device.vk().create_image_view(&info, None) } { Ok(view) => view, Err(err) => { log::error!("vkCreateImageView returned {:?} in GlobalImage::create_image", err); unsafe { device.get_allocator().destroy_image(image, allocation) } return Err(GlobalObjectCreateError::Vulkan(err)); } }; Ok((image, allocation, sampler_view)) } } impl PartialEq for GlobalImage { fn eq(&self, other: &Self) -> bool { self.id.eq(&other.id) } } impl Eq for GlobalImage { } impl PartialOrd for GlobalImage { fn partial_cmp(&self, other: &Self) -> Option { self.id.partial_cmp(&other.id) } } impl Ord for GlobalImage { fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) } } impl Hash for GlobalImage { fn hash(&self, state: &mut H) { self.id.hash(state) } } impl Drop for GlobalImage { fn drop(&mut self) { let device = self.share.get_device(); unsafe { device.vk().destroy_image_view(self.sampler_view, None); device.get_allocator().destroy_image(self.image, self.allocation); } } } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct SamplerInfo { pub mag_filter: vk::Filter, pub min_filter: vk::Filter, pub mipmap_mode: vk::SamplerMipmapMode, pub address_mode_u: vk::SamplerAddressMode, pub address_mode_v: vk::SamplerAddressMode, pub anisotropy_enable: bool, } ================================================ FILE: core/natives/src/renderer/emulator/immediate.rs ================================================ use std::collections::VecDeque; use std::panic::RefUnwindSafe; use std::ptr::NonNull; use std::sync::{Arc, Condvar, Mutex}; use ash::vk; use crate::allocator::{Allocation, HostAccess}; use crate::util::alloc::next_aligned; use crate::prelude::*; pub(super) struct ImmediatePool { buffer_queue: Mutex>>, ready_condvar: Condvar, } impl ImmediatePool { pub(super) fn new(device: Arc) -> Self { let mut buffer_queue = VecDeque::with_capacity(2); for _ in 0..2 { buffer_queue.push_back(Box::new(ImmediateBuffer::new(device.clone()))); } Self { buffer_queue: Mutex::new(buffer_queue), ready_condvar: Condvar::new(), } } pub(super) fn get_next_buffer(&self) -> Box { let mut guard = self.buffer_queue.lock().unwrap_or_else(|_| { log::error!("Poisoned queue mutex in ImmediatePool::get_next_buffer"); panic!() }); loop { if let Some(next) = guard.pop_front() { return next; } let (new_guard, timeout) = self.ready_condvar.wait_timeout(guard, std::time::Duration::from_secs(1)).unwrap_or_else(|_| { log::error!("Poisoned queue mutex in ImmediatePool::get_next_buffer after waiting for condvar"); panic!() }); guard = new_guard; if timeout.timed_out() { log::warn!("1s timeout hit while waiting for new buffer in ImmediatePool::get_next_buffer"); } } } pub(super) fn return_buffer(&self, mut buffer: Box) { buffer.reset(); let mut guard = self.buffer_queue.lock().unwrap_or_else(|_| { log::error!("Poisoned queue mutex in ImmediatePool::return_buffer"); panic!() }); guard.push_back(buffer); self.ready_condvar.notify_one(); } } impl RefUnwindSafe for ImmediatePool {} // Condvar is not RefUnwindSafe pub(super) struct ImmediateBuffer { device: Arc, current_buffer: Buffer, old_buffers: Vec, } impl ImmediateBuffer { const MIN_BUFFER_SIZE: vk::DeviceSize = 2u64.pow(24); // 16MB const OVER_ALLOCATION: u8 = 77; // 30% fn new(device: Arc) -> Self { let current_buffer = Buffer::new(device.clone(), Self::MIN_BUFFER_SIZE); Self { device, current_buffer, old_buffers: Vec::new(), } } pub(super) fn generate_copy_commands(&self, cmd: vk::CommandBuffer) { self.current_buffer.generate_copy_commands(cmd); for old_buffer in &self.old_buffers { old_buffer.generate_copy_commands(cmd); } } pub(super) fn reset(&mut self) { self.current_buffer.reset(); self.old_buffers.clear(); } pub(super) fn allocate(&mut self, data: &[u8], alignment: vk::DeviceSize) -> (vk::Buffer, vk::DeviceSize) { if let Some(info) = self.current_buffer.allocate(data, alignment) { info } else { let usage = self.get_current_usage(); let alloc_size = usage + (usage * (Self::OVER_ALLOCATION as u64) / (u8::MAX as u64)); let alloc_size = std::cmp::max(alloc_size, data.len() as u64); let alloc_size = std::cmp::max(alloc_size, Self::MIN_BUFFER_SIZE); let new_buffer = Buffer::new(self.device.clone(), alloc_size); self.old_buffers.push(std::mem::replace(&mut self.current_buffer, new_buffer)); self.current_buffer.allocate(data, alignment).unwrap() } } fn get_current_usage(&self) -> vk::DeviceSize { let mut usage = self.current_buffer.get_current_used_bytes(); for old_buffer in &self.old_buffers { usage += old_buffer.get_current_used_bytes(); } usage } } struct Buffer { device: Arc, main_buffer: vk::Buffer, mapped_memory: NonNull, size: vk::DeviceSize, current_offset: vk::DeviceSize, main_allocation: Allocation, staging: Option<(vk::Buffer, Allocation)>, } impl Buffer { fn new(device: Arc, size: vk::DeviceSize) -> Self { let (main_buffer, main_allocation, main_mapped) = Self::create_main_buffer(&device, size); let (staging, mapped_memory) = if let Some(mapped) = main_mapped { log::info!("Immediate buffer uses mapped memory"); (None, mapped) } else { log::info!("Immediate buffer uses staging memory"); let (staging_buffer, staging_allocation, staging_mapped) = Self::create_staging_buffer(&device, size); (Some((staging_buffer, staging_allocation)), staging_mapped) }; Self { device, main_buffer, mapped_memory, size, current_offset: 0, main_allocation, staging } } fn generate_copy_commands(&self, cmd: vk::CommandBuffer) { if let Some((staging_buffer, _)) = &self.staging { if self.current_offset != 0 { unsafe { self.device.vk().cmd_copy_buffer( cmd, *staging_buffer, self.main_buffer, &[vk::BufferCopy { src_offset: 0, dst_offset: 0, size: self.current_offset }] ) } } } } fn reset(&mut self) { self.current_offset = 0; } fn allocate(&mut self, bytes: &[u8], alignment: vk::DeviceSize) -> Option<(vk::Buffer, vk::DeviceSize)> { let aligned = next_aligned(self.current_offset, alignment); if aligned + (bytes.len() as vk::DeviceSize) > self.size { return None; } self.current_offset = aligned + (bytes.len() as vk::DeviceSize); let start = aligned as usize; let end = self.current_offset as usize; let dst = &mut unsafe { std::slice::from_raw_parts_mut(self.mapped_memory.as_ptr(), self.size as usize) }[start..end]; dst.copy_from_slice(bytes); Some((self.main_buffer, aligned)) } fn get_current_used_bytes(&self) -> vk::DeviceSize { self.current_offset } fn create_main_buffer(device: &DeviceContext, size: vk::DeviceSize) -> (vk::Buffer, Allocation, Option>) { let info = vk::BufferCreateInfo::builder() .size(size) .usage(vk::BufferUsageFlags::VERTEX_BUFFER | vk::BufferUsageFlags::INDEX_BUFFER | vk::BufferUsageFlags::TRANSFER_DST) .sharing_mode(vk::SharingMode::EXCLUSIVE); let (buffer, allocation, mapped) = unsafe { device.get_allocator().create_buffer(&info, HostAccess::RandomOptional, &format_args!("ImmediateMainBuffer")) }.unwrap_or_else(|| { log::error!("Failed to create main buffer."); panic!() }); (buffer, allocation, mapped) } fn create_staging_buffer(device: &DeviceContext, size: vk::DeviceSize) -> (vk::Buffer, Allocation, NonNull) { let info = vk::BufferCreateInfo::builder() .size(size) .usage(vk::BufferUsageFlags::TRANSFER_SRC) .sharing_mode(vk::SharingMode::EXCLUSIVE); let (buffer, allocation, mapped) = unsafe { device.get_allocator().create_buffer(&info, HostAccess::Random, &format_args!("ImmediateStagingBuffer")) }.unwrap_or_else(|| { log::error!("Failed to create staging buffer."); panic!() }); (buffer, allocation, mapped.unwrap()) } } unsafe impl Send for Buffer { // Needed because of NonNull } unsafe impl Sync for Buffer { // Needed because of NonNull } impl Drop for Buffer { fn drop(&mut self) { unsafe { self.device.get_allocator().destroy_buffer(self.main_buffer, self.main_allocation); if let Some((buffer, alloc)) = self.staging.take() { self.device.get_allocator().destroy_buffer(buffer, alloc) } } } } ================================================ FILE: core/natives/src/renderer/emulator/mc_shaders.rs ================================================ //! Structs used to process minecrafts uniforms and samplers use std::collections::HashMap; use std::fmt::Debug; use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}; use std::sync::{Arc, Mutex, Weak}; use ash::vk; use crate::define_uuid_type; use crate::prelude::*; define_uuid_type!(pub, ShaderId); pub trait ShaderDropListener { fn on_shader_drop(&self, id: ShaderId); } pub struct Shader { id: ShaderId, vertex_format: VertexFormat, used_uniforms: McUniform, weak: Weak, listeners: Mutex>>, } impl Shader { pub fn new(vertex_format: VertexFormat, used_uniforms: McUniform) -> Arc { Arc::new_cyclic(|weak| { Self { id: ShaderId::new(), vertex_format, used_uniforms, weak: weak.clone(), listeners: Mutex::new(HashMap::new()), } }) } pub fn get_id(&self) -> ShaderId { self.id } pub fn get_vertex_format(&self) -> &VertexFormat { &self.vertex_format } pub fn get_used_uniforms(&self) -> McUniform { self.used_uniforms } /// Registers a drop listener to this shader. If this shader is dropped the listener will be called. /// /// The returned [`ShaderListener`] is used keep track of the liveliness of the listener. If it is /// dropped the listener will be removed from the shader. pub fn register_drop_listener(&self, listener: &Arc) -> ShaderListener { let id = UUID::new(); let mut guard = self.listeners.lock().unwrap(); guard.insert(id, Arc::downgrade(listener)); ShaderListener { shader: self.weak.clone(), listener_id: id, } } /// Called by [`ShaderRef`] when it is dropped to remove any dangling listeners. fn remove_listener(&self, id: UUID) { let mut guard = self.listeners.lock().unwrap(); guard.remove(&id); } } pub struct ShaderListener { shader: Weak, listener_id: UUID, } impl Drop for ShaderListener { fn drop(&mut self) { if let Some(shader) = self.shader.upgrade() { shader.remove_listener(self.listener_id) } } } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct McUniform(u64); impl McUniform { #[inline] pub const fn empty() -> Self { Self(0) } #[inline] pub const fn from_raw(raw: u64) -> Self { Self(raw) } #[inline] pub const fn as_raw(&self) -> u64 { self.0 } #[inline] pub const fn is_empty(&self) -> bool { self.0 == Self::empty().0 } #[inline] pub const fn intersects(&self, other: &Self) -> bool { !Self(self.0 & other.0).is_empty() } #[inline] pub const fn contains(&self, other: &Self) -> bool { (self.0 & other.0) == other.0 } pub const MODEL_VIEW_MATRIX: Self = Self::from_raw(1u64); pub const PROJECTION_MATRIX: Self = Self::from_raw(1u64 << 1); pub const INVERSE_VIEW_ROTATION_MATRIX: Self = Self::from_raw(1u64 << 2); pub const TEXTURE_MATRIX: Self = Self::from_raw(1u64 << 3); pub const SCREEN_SIZE: Self = Self::from_raw(1u64 << 4); pub const COLOR_MODULATOR: Self = Self::from_raw(1u64 << 5); pub const LIGHT0_DIRECTION: Self = Self::from_raw(1u64 << 6); pub const LIGHT1_DIRECTION: Self = Self::from_raw(1u64 << 7); pub const FOG_START: Self = Self::from_raw(1u64 << 8); pub const FOG_END: Self = Self::from_raw(1u64 << 9); pub const FOG_COLOR: Self = Self::from_raw(1u64 << 10); pub const FOG_SHAPE: Self = Self::from_raw(1u64 << 11); pub const LINE_WIDTH: Self = Self::from_raw(1u64 << 12); pub const GAME_TIME: Self = Self::from_raw(1u64 << 13); pub const CHUNK_OFFSET: Self = Self::from_raw(1u64 << 14); } impl BitOr for McUniform { type Output = McUniform; #[inline] fn bitor(self, rhs: Self) -> Self::Output { Self(self.0 | rhs.0) } } impl BitOrAssign for McUniform { #[inline] fn bitor_assign(&mut self, rhs: Self) { *self = *self | rhs } } impl BitAnd for McUniform { type Output = McUniform; #[inline] fn bitand(self, rhs: Self) -> Self::Output { Self(self.0 & rhs.0) } } impl BitAndAssign for McUniform { #[inline] fn bitand_assign(&mut self, rhs: Self) { *self = *self & rhs } } impl BitXor for McUniform { type Output = McUniform; #[inline] fn bitxor(self, rhs: Self) -> Self::Output { Self(self.0 ^ rhs.0) } } impl BitXorAssign for McUniform { #[inline] fn bitxor_assign(&mut self, rhs: Self) { *self = *self ^ rhs } } impl Not for McUniform { type Output = McUniform; #[inline] fn not(self) -> Self::Output { Self(self.0.not()) } } #[derive(Copy, Clone, Debug)] pub enum McUniformData { ModelViewMatrix(Mat4f32), ProjectionMatrix(Mat4f32), InverseViewRotationMatrix(Mat4f32), TextureMatrix(Mat4f32), ScreenSize(Vec2f32), ColorModulator(Vec4f32), Light0Direction(Vec3f32), Light1Direction(Vec3f32), FogStart(f32), FogEnd(f32), FogColor(Vec4f32), FogShape(u32), LineWidth(f32), GameTime(f32), ChunkOffset(Vec3f32), } #[repr(C)] #[derive(Copy, Clone, Debug, Default)] pub struct DevUniform { #[allow(unused)] pub projection_matrix: Mat4f32, #[allow(unused)] pub model_view_matrix: Mat4f32, #[allow(unused)] pub chunk_offset: Vec3f32, _padding0: [u8; 4], } const_assert_eq!(std::mem::size_of::(), 144); const_assert_eq!(std::mem::size_of::() % 16, 0); // std140 size must be multiple of vec4 #[derive(Copy, Clone, Debug)] pub struct VertexFormatEntry { pub offset: u32, pub format: vk::Format, } #[derive(Copy, Clone, Debug)] pub struct VertexFormat { pub stride: u32, pub position: VertexFormatEntry, pub normal: Option, pub color: Option, pub uv0: Option, pub uv1: Option, pub uv2: Option, } ================================================ FILE: core/natives/src/renderer/emulator/mod.rs ================================================ //! The emulator renderer renders objects in a minecraft compatible manner. //! //! The [`EmulatorRenderer`] provides the necessary infrastructure for rendering but does not render //! itself. Responsibilities includes management of long living resources such as static meshes / //! textures and efficient uploading of short lived immediate objects used only inside one pass. //! Rendering itself, is performed by [`EmulatorPipeline`] instances. This maximises flexibility of //! the renderer. //! //! All rendering is done inside passes using a [`PassRecorder`]. Every pass uses a single //! [`EmulatorPipeline`] to render its objects. Passes do not have to have a one to one //! correspondence with frames. It is fully possible to use multiple passes and then combining the //! output of each externally to form a frame. Or use passes asynchronously to the main render loop. //! However currently b4d uses a single pass to render a single frame. mod immediate; mod worker; mod global_objects; mod pass; pub mod pipeline; pub mod debug_pipeline; pub mod mc_shaders; mod descriptors; mod share; mod staging; use std::fmt::{Debug, Formatter}; use std::panic::RefUnwindSafe; use std::sync::Arc; use ash::vk; use bytemuck::cast_slice; use crate::renderer::emulator::worker::run_worker; use crate::renderer::emulator::pipeline::EmulatorPipeline; use crate::prelude::*; pub use global_objects::{GlobalMesh, GlobalImage, ImageData, SamplerInfo}; pub use pass::PassId; pub use pass::PassRecorder; pub use pass::ImmediateMeshId; use share::Share; use crate::renderer::emulator::mc_shaders::{McUniform, Shader, ShaderId, VertexFormat}; use crate::util::format::Format; pub struct EmulatorRenderer { share: Arc, placeholder_image: Arc, placeholder_sampler: SamplerInfo, worker: std::thread::JoinHandle<()>, } impl EmulatorRenderer { pub(crate) fn new(device: Arc) -> Self { let share = Arc::new(Share::new(device.clone())); let share2 = share.clone(); let worker = std::thread::spawn(move || { std::panic::catch_unwind(|| { run_worker(device,share2); }).unwrap_or_else(|_| { log::error!("Emulator worker panicked!"); std::process::exit(1); }) }); let placeholder_image = Self::create_placeholder_image(share.clone()); let placeholder_sampler = SamplerInfo { mag_filter: vk::Filter::LINEAR, min_filter: vk::Filter::LINEAR, mipmap_mode: vk::SamplerMipmapMode::LINEAR, address_mode_u: vk::SamplerAddressMode::REPEAT, address_mode_v: vk::SamplerAddressMode::REPEAT, anisotropy_enable: false }; Self { share, placeholder_image, placeholder_sampler, worker, } } pub fn get_device(&self) -> &Arc { self.share.get_device() } pub fn create_global_mesh(&self, data: &MeshData) -> Arc { GlobalMesh::new(self.share.clone(), data).unwrap() } pub fn create_global_image(&self, size: Vec2u32, format: &'static Format) -> Arc { GlobalImage::new(self.share.clone(), size, 1, format).unwrap() } pub fn create_global_image_mips(&self, size: Vec2u32, mip_levels: u32, format: &'static Format) -> Arc { GlobalImage::new(self.share.clone(), size, mip_levels, format).unwrap() } pub fn create_shader(&self, vertex_format: &VertexFormat, used_uniforms: McUniform) -> ShaderId { self.share.create_shader(vertex_format, used_uniforms) } pub fn drop_shader(&self, id: ShaderId) { self.share.drop_shader(id) } pub fn get_shader(&self, id: ShaderId) -> Option> { self.share.get_shader(id) } pub fn start_pass(&self, pipeline: Arc) -> PassRecorder { PassRecorder::new(self.share.clone(), pipeline, self.placeholder_image.clone(), &self.placeholder_sampler) } fn create_placeholder_image(share: Arc) -> Arc { let size = Vec2u32::new(256, 256); let mut data: Box<[_]> = std::iter::repeat([0u8, 0u8, 0u8, 255u8]).take((size[0] as usize) * (size[1] as usize)).collect(); for x in 0..(size[0] as usize) { for y in 0..(size[1] as usize) { if ((x / 128) + (y / 128)) % 2 == 0 { data[(y * (size[0] as usize)) + x] = [255u8, 0u8, 255u8, 255u8]; } } } let bytes = cast_slice(data.as_ref()); let info = ImageData { data: bytes, row_stride: 0, offset: Vec2u32::new(0, 0), extent: size }; let image = GlobalImage::new(share, size, 1, &Format::R8G8B8A8_SRGB).unwrap(); image.update_regions(std::slice::from_ref(&info)); image } } impl PartialEq for EmulatorRenderer { fn eq(&self, other: &Self) -> bool { self.share.eq(&other.share) } } impl Eq for EmulatorRenderer { } impl RefUnwindSafe for EmulatorRenderer { // Join handle is making issues } pub struct MeshData<'a> { pub vertex_data: &'a [u8], pub index_data: &'a [u8], pub vertex_stride: u32, pub index_count: u32, pub index_type: vk::IndexType, pub primitive_topology: vk::PrimitiveTopology, } impl<'a> MeshData<'a> { pub fn get_index_size(&self) -> u32 { match self.index_type { vk::IndexType::UINT8_EXT => 1u32, vk::IndexType::UINT16 => 2u32, vk::IndexType::UINT32 => 4u32, _ => { log::error!("Invalid index type"); panic!() } } } } impl<'a> Debug for MeshData<'a> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("MeshData") .field("vertex_data.len()", &self.vertex_data.len()) .field("index_data.len()", &self.index_data.len()) .field("vertex_stride", &self.vertex_stride) .field("index_count", &self.index_count) .field("index_type", &self.index_type) .field("primitive_topology", &self.primitive_topology) .finish() } } ================================================ FILE: core/natives/src/renderer/emulator/pass.rs ================================================ use std::collections::HashSet; use std::sync::Arc; use ash::vk; use crate::renderer::emulator::immediate::ImmediateBuffer; use crate::renderer::emulator::{GlobalImage, GlobalMesh, MeshData}; use crate::renderer::emulator::global_objects::{GlobalImageId, SamplerInfo}; use crate::renderer::emulator::worker::WorkerTask; use crate::renderer::emulator::mc_shaders::{McUniformData, ShaderId}; use crate::renderer::emulator::pipeline::{DrawTask, EmulatorOutput, EmulatorPipeline, PipelineTask}; use crate::renderer::emulator::share::Share; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct PassId(u64); impl PassId { pub fn from_raw(id: u64) -> Self { Self(id) } pub fn get_raw(&self) -> u64 { self.0 } } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct ImmediateMeshId(u32); impl ImmediateMeshId { pub fn form_raw(id: u32) -> Self { Self(id) } pub fn get_raw(&self) -> u32 { self.0 } } pub struct PassRecorder { id: PassId, share: Arc, used_shaders: HashSet, used_global_image: HashSet, immediate_meshes: Vec, immediate_buffer: Option>, #[allow(unused)] // We just need to keep the pipeline alive pipeline: Arc, } impl PassRecorder { pub(super) fn new(share: Arc, pipeline: Arc, placeholder_image: Arc, placeholder_sampler: &SamplerInfo) -> Self { let id = share.try_start_pass_id().unwrap_or_else(|| { log::error!("Attempted to start pass with an already running pass!"); panic!(); }); let id = PassId::from_raw(id); let immediate_buffer = Some(share.get_next_immediate_buffer()); let placeholder_sampler = placeholder_image.get_sampler(placeholder_sampler); share.push_task(WorkerTask::StartPass(id, pipeline.clone(), pipeline.start_pass(), placeholder_image, placeholder_sampler)); Self { id, share, used_shaders: HashSet::new(), used_global_image: HashSet::new(), immediate_meshes: Vec::with_capacity(128), immediate_buffer, pipeline, } } pub fn use_output(&mut self, output: Box) { self.share.push_task(WorkerTask::UseOutput(output)); } pub fn update_uniform(&mut self, data: &McUniformData, shader: ShaderId) { self.use_shader(shader); self.share.push_task(WorkerTask::PipelineTask(PipelineTask::UpdateUniform(shader, *data))) } pub fn update_texture(&mut self, index: u32, image: &Arc, sampler_info: &SamplerInfo, shader: ShaderId) { self.use_shader(shader); let view = image.get_sampler_view(); let sampler = image.get_sampler(sampler_info); if self.used_global_image.insert(image.get_id()) { self.share.push_task(WorkerTask::UseGlobalImage(image.clone())); } self.share.push_task(WorkerTask::PipelineTask(PipelineTask::UpdateTexture(shader, index, view, sampler))); } pub fn upload_immediate(&mut self, data: &MeshData) -> ImmediateMeshId { let index_size = data.get_index_size(); let immediate = self.immediate_buffer.as_mut().unwrap(); let (vertex_buffer, vertex_offset) = immediate.allocate(data.vertex_data, data.vertex_stride as vk::DeviceSize); let (index_buffer, index_offset) = immediate.allocate(data.index_data, index_size as vk::DeviceSize); let id = self.immediate_meshes.len() as u32; self.immediate_meshes.push(ImmediateMeshInfo { vertex_buffer, index_buffer, vertex_offset: (vertex_offset / (data.vertex_stride as vk::DeviceSize)) as i32, first_index: (index_offset / (index_size as vk::DeviceSize)) as u32, index_type: data.index_type, index_count: data.index_count, primitive_topology: data.primitive_topology }); ImmediateMeshId::form_raw(id) } pub fn draw_immediate(&mut self, id: ImmediateMeshId, shader: ShaderId, depth_write_enable: bool) { self.use_shader(shader); let mesh_data = self.immediate_meshes.get(id.get_raw() as usize).unwrap(); let draw_task = DrawTask { vertex_buffer: mesh_data.vertex_buffer, index_buffer: mesh_data.index_buffer, vertex_offset: mesh_data.vertex_offset, first_index: mesh_data.first_index, index_type: mesh_data.index_type, index_count: mesh_data.index_count, shader, primitive_topology: mesh_data.primitive_topology, depth_write_enable, }; self.share.push_task(WorkerTask::PipelineTask(PipelineTask::Draw(draw_task))); } pub fn draw_global(&mut self, mesh: Arc, shader: ShaderId, depth_write_enable: bool) { mesh.update_used_in(self.id); self.use_shader(shader); let draw_info = mesh.get_draw_info(); let draw_task = DrawTask { vertex_buffer: draw_info.buffer, index_buffer: draw_info.buffer, vertex_offset: 0, first_index: draw_info.first_index, index_type: draw_info.index_type, index_count: draw_info.index_count, shader, primitive_topology: draw_info.primitive_topology, depth_write_enable, }; self.share.push_task(WorkerTask::UseGlobalMesh(mesh)); self.share.push_task(WorkerTask::PipelineTask(PipelineTask::Draw(draw_task))); } fn use_shader(&mut self, shader: ShaderId) { if self.used_shaders.insert(shader) { self.pipeline.inc_shader_used(shader); self.share.push_task(WorkerTask::UseShader(shader)); } } } impl Drop for PassRecorder { fn drop(&mut self) { self.share.push_task(WorkerTask::EndPass(self.immediate_buffer.take().unwrap())); self.share.end_pass_id(); } } struct ImmediateMeshInfo { vertex_buffer: vk::Buffer, index_buffer: vk::Buffer, vertex_offset: i32, first_index: u32, index_type: vk::IndexType, index_count: u32, primitive_topology: vk::PrimitiveTopology, } ================================================ FILE: core/natives/src/renderer/emulator/pipeline.rs ================================================ use std::hash::Hash; use std::panic::{RefUnwindSafe, UnwindSafe}; use std::sync::{Arc, Weak}; use ash::prelude::VkResult; use ash::vk; use bumpalo::Bump; use crate::device::device::Queue; use crate::device::device_utils::BlitPass; use crate::device::surface::{AcquiredImageInfo, SurfaceSwapchain}; use crate::prelude::*; use crate::renderer::emulator::mc_shaders::{McUniformData, ShaderId}; pub use super::worker::SubmitRecorder; pub use super::worker::PooledObjectProvider; /// A [`EmulatorPipeline`] performs the actual rendering inside a pass. /// /// To define how objects should be rendered a pipeline can define multiple types. The meaning of /// each type is pipeline dependant however the [`EmulatorRenderer`] requires some information about /// the type which must be provided through [`EmulatorPipeline::get_type_info`]. pub trait EmulatorPipeline: Send + Sync + UnwindSafe + RefUnwindSafe { /// Called internally by the emulator renderer when a pass is started. All rendering will be /// performed using the returned object. /// /// This function must be called on thread with [`EmulatorRenderer::start_pass`] and thus may be /// used to block execution. For example to prevent an infinite build up of un-submitted passes /// if the user submits tasks faster than the gpu can process them. fn start_pass(&self) -> Box; /// Returns the size and a list of image views which can be used as source images for samplers /// for the output of the pipeline. /// /// **This is a temporary api and needs a rework to improve flexibility and elegance** fn get_output(&self) -> (Vec2u32, &[vk::ImageView]); /// Called internally by the emulator renderer when pass uses a shader for the first time. /// A corresponding call to [`dec_shader_used`] will be performed after the corresponding pass /// has been dropped. /// /// It is guaranteed that this function will be called before the corresponding pass receives /// any task using this shader. /// /// This can be used to keep track of used shaders globally to manage vulkan pipelines. fn inc_shader_used(&self, shader: ShaderId); /// Called internally by the emulator renderer after a pass is dropped for each shader the pass /// used. Every call to this function must have had a earlier call to [`inc_shader_used`]. /// /// This can be used to keep track of used shaders globally to manage vulkan pipelines. fn dec_shader_used(&self, shader: ShaderId); } /// Represents one execution of a [`EmulatorPipeline`]. /// /// A pass is processed in 3 stages. /// 1. Uninitialized: The pass has just been created by calling [`EmulatorPipeline::start_pass`]. /// 2. Recording: The pass is currently recording tasks. /// 3. Submitted: All command buffers have been submitted for execution. /// /// Any instance of this struct will not be dropped until all submitted command buffers have /// finished execution. If it is dropped it may assume that all used resources are safe to be /// reused. A pass may be aborted at any moment for any reason. pub trait EmulatorPipelinePass { /// Called to initialize internal state. /// /// This transitions the pass from the uninitialized state to the recording state. /// /// The queue which will be used to submit command buffers is provided. All resources (i.e. /// buffers, images etc.) passed to this pass will be owned by this queue family. /// /// A placeholder image is provided which can be used for sampled images. This image must only /// be used in submits made by [`EmulatorPipelinePass::record`]. fn init(&mut self, queue: &Queue, obj: &mut PooledObjectProvider, placeholder_image: vk::ImageView, placeholder_sampler: vk::Sampler); /// Called to process a task. /// /// Must only be called while the pass is in the recording state. fn process_task(&mut self, task: &PipelineTask, obj: &mut PooledObjectProvider); /// Called to record any necessary command buffer submissions for the execution of the pass. /// The recorded submits will be submitted by the calling code. /// /// This transitions the pass from the recording state to the submitted state. fn record<'a>(&mut self, obj: &mut PooledObjectProvider, submits: &mut SubmitRecorder<'a>, alloc: &'a Bump); /// Returns the index into the image view list returned by [`EmulatorPipeline::get_output`] /// determining which image view should be used to access the output of the pass. /// /// **This is a temporary api and needs a rework to improve flexibility and elegance** fn get_output_index(&self) -> usize; /// Called to retrieve a list of fences used to wait for internally submitted commands. /// /// In order to guarantee that any submissions made by the pass internally have completed /// execution this function returns a list of fences such that waiting on all fences implies /// that all submissions are done executing and resources can be safely reused. /// /// If any other function (except [`drop`]) of this pass are called after this function, the /// list of returned fences becomes invalid and a new call to this function must be made. /// /// TODO this is currently not used by the worker fn get_internal_fences(&self, fences: &mut Vec); } #[derive(Copy, Clone, Debug)] pub enum PipelineTask { UpdateUniform(ShaderId, McUniformData), UpdateTexture(ShaderId, u32, vk::ImageView, vk::Sampler), Draw(DrawTask), } #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct DrawTask { pub vertex_buffer: vk::Buffer, pub index_buffer: vk::Buffer, pub vertex_offset: i32, pub first_index: u32, pub index_type: vk::IndexType, pub index_count: u32, pub shader: ShaderId, pub primitive_topology: vk::PrimitiveTopology, pub depth_write_enable: bool, } /// Used to process the output of a [`EmulatorPipelinePass`]. /// /// Any instance of this struct will not be dropped until all submitted command buffers have /// finished execution. If it is dropped it may assume that all used resources are safe to be /// reused. /// /// **This is a temporary api and needs a rework to improve flexibility and elegance** pub trait EmulatorOutput { /// Initializes the output to use the specified [`EmulatorPipelinePass`]. fn init(&mut self, pass: &dyn EmulatorPipelinePass, obj: &mut PooledObjectProvider); /// Records any necessary submissions. /// The recorded submits will be submitted by the calling code. fn record<'a>(&mut self, obj: &mut PooledObjectProvider, submits: &mut SubmitRecorder<'a>, alloc: &'a Bump); /// Called after the submits recorded by [`EmulatorOutput::record`] have been submitted for /// execution. This is particularly useful to perform any queue present operations. fn on_post_submit(&mut self, queue: &Queue); } /// A utility struct providing a [`BlitPass`] for the output of a [`EmulatorPipeline`]. pub struct OutputUtil { #[allow(unused)] // We just need to keep the pipeline alive pipeline: Arc, descriptor_pool: vk::DescriptorPool, descriptor_sets: Box<[vk::DescriptorSet]>, blit_pass: BlitPass, } impl OutputUtil { pub fn new(device: &DeviceContext, pipeline: Arc, format: vk::Format, final_layout: vk::ImageLayout) -> Self { let (_, sampler_views) = pipeline.get_output(); let blit_pass = device.get_utils().blit_utils().create_blit_pass(format, vk::AttachmentLoadOp::DONT_CARE, vk::ImageLayout::UNDEFINED, final_layout); let descriptor_pool = Self::create_descriptor_pool(device, sampler_views.len()); let descriptor_sets = blit_pass.create_descriptor_sets(descriptor_pool, sampler_views).unwrap().into_boxed_slice(); Self { pipeline, descriptor_pool, descriptor_sets, blit_pass } } /// Creates a framebuffer which can be used as a draw target for the blit pass. /// /// The returned framebuffer is fully owned by the calling code and must be destroyed before /// this struct is dropped. pub fn create_framebuffer(&self, image_view: vk::ImageView, size: Vec2u32) -> VkResult { self.blit_pass.create_framebuffer(image_view, size) } /// Records one execution of the blit pass. /// /// The pipeline index is the index returned by [`EmulatorPipelinePass::get_output_index`]. pub fn record(&self, command_buffer: vk::CommandBuffer, output_framebuffer: vk::Framebuffer, output_size: Vec2u32, pipeline_index: usize) { self.blit_pass.record_blit( command_buffer, self.descriptor_sets[pipeline_index], output_framebuffer, output_size, None ) } fn create_descriptor_pool(device: &DeviceContext, sampler_count: usize) -> vk::DescriptorPool { let sizes = [ vk::DescriptorPoolSize { ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, descriptor_count: sampler_count as u32, } ]; let info = vk::DescriptorPoolCreateInfo::builder() .max_sets(sampler_count as u32) .pool_sizes(&sizes); unsafe { device.vk().create_descriptor_pool(&info, None) }.unwrap() } } impl Drop for OutputUtil { fn drop(&mut self) { unsafe { self.blit_pass.get_device().vk.destroy_descriptor_pool(self.descriptor_pool, None); } } } /// A [`EmulatorOutput`] implementation which copes the output image to a swapchain image and /// presents it. pub struct SwapchainOutput { weak: Weak, swapchain: Arc, util: OutputUtil, framebuffers: Box<[vk::Framebuffer]>, } impl SwapchainOutput { pub fn new(device: &DeviceContext, pipeline: Arc, swapchain: Arc) -> Arc { let util = OutputUtil::new(device, pipeline, swapchain.get_image_format().format, vk::ImageLayout::PRESENT_SRC_KHR); let framebuffers = swapchain.get_images().iter().map(|image| { util.create_framebuffer(image.get_framebuffer_view(), swapchain.get_image_size()).unwrap() }).collect(); Arc::new_cyclic(|weak| Self { weak: weak.clone(), swapchain, util, framebuffers }) } /// Attempts to acquire a new image from the swapchain blocking until it does. /// /// Returns [`None`] if the swapchain is out of date. /// /// If it successfully acquires a image returns a [`EmulatorOutput`] instance for the image as /// well as a boolean flag set to true if the swapchain is suboptimal. pub fn next_image(&self) -> Option<(Box, bool)> { loop { let arc = self.weak.upgrade().unwrap(); match self.swapchain.acquire_next_image(1000000000, None) { Ok((info, suboptimal)) => return Some((Box::new(SwapchainOutputInstance::new(arc, info)), suboptimal)), Err(vk::Result::TIMEOUT) => log::warn!("1s timeout reached while waiting for next swapchain image in SwapchainOutput::next_image"), Err(err) => { log::error!("vkAcquireNextImageKHR returned {:?} in SwapchainOutput::next_image", err); panic!() } } } } } impl Drop for SwapchainOutput { fn drop(&mut self) { let device = self.swapchain.get_device(); unsafe { for framebuffer in self.framebuffers.iter() { device.vk.destroy_framebuffer(*framebuffer, None); } } } } struct SwapchainOutputInstance { output: Arc, image_info: AcquiredImageInfo, pipeline_index: Option, } impl SwapchainOutputInstance { fn new(output: Arc, image_info: AcquiredImageInfo) -> Self { Self { output, image_info, pipeline_index: None, } } } impl EmulatorOutput for SwapchainOutputInstance { fn init(&mut self, pass: &dyn EmulatorPipelinePass, _: &mut PooledObjectProvider) { self.pipeline_index = Some(pass.get_output_index()); } fn record<'a>(&mut self, obj: &mut PooledObjectProvider, submits: &mut SubmitRecorder<'a>, alloc: &'a Bump) { let cmd = obj.get_begin_command_buffer().unwrap(); self.output.util.record(cmd, self.output.framebuffers[self.image_info.image_index as usize], self.output.swapchain.get_image_size(), self.pipeline_index.unwrap()); unsafe { self.output.swapchain.get_device().vk.end_command_buffer(cmd) }.unwrap(); let waits = alloc.alloc([ vk::SemaphoreSubmitInfo::builder() .semaphore(self.image_info.acquire_semaphore.semaphore.get_handle()) .value(self.image_info.acquire_semaphore.value.unwrap_or(0)) .build() ]); let signals = alloc.alloc([ vk::SemaphoreSubmitInfo::builder() .semaphore(self.image_info.acquire_ready_semaphore.semaphore.get_handle()) .value(self.image_info.acquire_ready_semaphore.value.unwrap_or(0)) .build(), vk::SemaphoreSubmitInfo::builder() .semaphore(self.output.swapchain.get_images()[self.image_info.image_index as usize].get_present_semaphore().get_handle()) .build() ]); let commands = alloc.alloc([ vk::CommandBufferSubmitInfo::builder() .command_buffer(cmd) .build() ]); submits.push(vk::SubmitInfo2::builder() .wait_semaphore_infos(waits) .command_buffer_infos(commands) .signal_semaphore_infos(signals) ); } fn on_post_submit(&mut self, queue: &Queue) { let present_semaphore = self.output.swapchain.get_images()[self.image_info.image_index as usize].get_present_semaphore().get_handle(); let guard = self.output.swapchain.get_swapchain().lock().unwrap(); let present_info = vk::PresentInfoKHR::builder() .wait_semaphores(std::slice::from_ref(&present_semaphore)) .swapchains(std::slice::from_ref(&*guard)) .image_indices(std::slice::from_ref(&self.image_info.image_index)); unsafe { queue.present(&present_info) }.unwrap(); } } ================================================ FILE: core/natives/src/renderer/emulator/share.rs ================================================ use std::sync::{Arc, Condvar, Mutex}; use std::time::{Duration, Instant}; use std::panic::RefUnwindSafe; use std::collections::{HashMap, VecDeque}; use std::sync::atomic::AtomicU64; use ash::vk; use crate::renderer::emulator::descriptors::DescriptorPool; use crate::renderer::emulator::worker::WorkerTask; use crate::renderer::emulator::mc_shaders::{McUniform, Shader, ShaderId, VertexFormat}; use crate::prelude::*; use crate::renderer::emulator::immediate::{ImmediateBuffer, ImmediatePool}; use crate::renderer::emulator::staging::StagingMemoryPool; pub(super) struct Share { id: UUID, device: Arc, current_pass: AtomicU64, staging_memory: Mutex, immediate_buffers: ImmediatePool, shader_database: Mutex>>, descriptors: Mutex, channel: Mutex, signal: Condvar, } impl Share { const PASS_ID_ACTIVE_BIT: u64 = 1u64 << 63; pub(super) fn new(device: Arc) -> Self { let queue = device.get_main_queue(); let staging_memory = StagingMemoryPool::new(device.clone()); let immediate_buffers = ImmediatePool::new(device.clone()); let descriptors = Mutex::new(DescriptorPool::new(device.clone())); Self { id: UUID::new(), device, current_pass: AtomicU64::new(0), staging_memory: Mutex::new(staging_memory), immediate_buffers, shader_database: Mutex::new(HashMap::new()), descriptors, channel: Mutex::new(Channel::new()), signal: Condvar::new(), } } pub(super) fn get_device(&self) -> &Arc { &self.device } pub(super) fn get_staging_pool(&self) -> &Mutex { &self.staging_memory } pub(super) fn create_shader(&self, vertex_format: &VertexFormat, used_uniforms: McUniform) -> ShaderId { let shader = Shader::new(*vertex_format, used_uniforms); let id = shader.get_id(); let mut guard = self.shader_database.lock().unwrap(); guard.insert(id, shader); id } pub(super) fn drop_shader(&self, id: ShaderId) { let mut guard = self.shader_database.lock().unwrap(); guard.remove(&id); } pub(super) fn get_shader(&self, id: ShaderId) -> Option> { let guard = self.shader_database.lock().unwrap(); guard.get(&id).cloned() } pub(super) fn get_current_pass_id(&self) -> Option { let id = self.current_pass.load(std::sync::atomic::Ordering::Acquire); if (id & Self::PASS_ID_ACTIVE_BIT) == Self::PASS_ID_ACTIVE_BIT { Some(id & !Self::PASS_ID_ACTIVE_BIT) } else { None } } pub(super) fn try_start_pass_id(&self) -> Option { loop { let old_id = self.current_pass.load(std::sync::atomic::Ordering::Acquire); if (old_id & Self::PASS_ID_ACTIVE_BIT) == Self::PASS_ID_ACTIVE_BIT { return None; } let new_id = old_id + 1; if (new_id & Self::PASS_ID_ACTIVE_BIT) == Self::PASS_ID_ACTIVE_BIT { log::error!("Pass id overflow. This is either a bug or this application has been running for a few thousand years"); panic!() } if let Ok(_) = self.current_pass.compare_exchange( old_id, new_id | Self::PASS_ID_ACTIVE_BIT, std::sync::atomic::Ordering::SeqCst, std::sync::atomic::Ordering::Acquire ) { return Some(new_id); } } } pub(super) fn end_pass_id(&self) { let old_id = self.current_pass.load(std::sync::atomic::Ordering::Acquire); if (old_id & Self::PASS_ID_ACTIVE_BIT) == 0 { log::error!("Called Share::end_pass_id with no active pass!"); panic!() } let new_id = old_id & !Self::PASS_ID_ACTIVE_BIT; self.current_pass.compare_exchange(old_id, new_id, std::sync::atomic::Ordering::SeqCst, std::sync::atomic::Ordering::Acquire).unwrap_or_else(|_| { log::error!("Current pass id has been modified while Share::end_pass_id is running!"); panic!(); }); } pub(super) fn get_next_immediate_buffer(&self) -> Box { self.immediate_buffers.get_next_buffer() } pub(super) fn return_immediate_buffer(&self, buffer: Box) { self.immediate_buffers.return_buffer(buffer); } pub(super) fn allocate_uniform(&self, data: &[u8]) -> (vk::Buffer, vk::DeviceSize) { self.descriptors.lock().unwrap().allocate_uniform(data) } pub(super) fn push_task(&self, task: WorkerTask) { self.channel.lock().unwrap().queue.push_back(task); self.signal.notify_one(); } pub(super) fn try_get_next_task_timeout(&self, timeout: Duration) -> NextTaskResult { let start = Instant::now(); let mut guard = self.channel.lock().unwrap_or_else(|_| { log::error!("Poisoned channel mutex in Share::try_get_next_task!"); panic!() }); loop { if let Some(task) = guard.queue.pop_front() { return NextTaskResult::Ok(task); } let diff = (start + timeout).saturating_duration_since(Instant::now()); if diff.is_zero() { return NextTaskResult::Timeout; } let (new_guard, timeout) = self.signal.wait_timeout(guard, diff).unwrap_or_else(|_| { log::error!("Poisoned channel mutex in Share::try_get_next_task!"); panic!() }); guard = new_guard; if timeout.timed_out() { return NextTaskResult::Timeout; } } } } impl PartialEq for Share { fn eq(&self, other: &Self) -> bool { self.id.eq(&other.id) } } impl Eq for Share { } // Condvar issues impl RefUnwindSafe for Share { } pub(in crate::renderer::emulator) enum NextTaskResult { Ok(WorkerTask), Timeout, } struct Channel { queue: VecDeque, } impl Channel { fn new() -> Self { Self { queue: VecDeque::new() } } } ================================================ FILE: core/natives/src/renderer/emulator/staging.rs ================================================ use std::ptr::NonNull; use std::sync::Arc; use ash::vk; use crate::allocator::{Allocation, HostAccess}; use crate::prelude::DeviceContext; use crate::util::alloc::RingAllocator; pub struct StagingAllocationId { buffer_id: u16, slot_id: u16, } pub struct StagingMemoryPool { device: Arc, next_buffer_id: u16, current_buffer_id: u16, current_buffer: StagingBuffer, old_buffers: Vec<(u16, StagingBuffer)>, /// Multiplier applied to the size of a new backing buffer allocation. /// `0` is a multiplier of 1.0 and [`u8::MAX`] a multiplier of 2.0 over_allocation: u8, /// The threshold of used memory at which point a backing buffer is reduced in size. /// `0` defines a threshold of `0%` i.e. never reduce and [`u8::MAX`] a threshold of `100%` i.e. /// always reduce. reduce_threshold: u8, } impl StagingMemoryPool { const MIN_BUFFER_SIZE: vk::DeviceSize = 2u64.pow(24); // 16MB pub(super) fn new(device: Arc) -> Self { let current_buffer = StagingBuffer::new(device.clone(), Self::MIN_BUFFER_SIZE); Self { device, next_buffer_id: 1, current_buffer_id: 0, current_buffer, old_buffers: Vec::new(), over_allocation: 76, reduce_threshold: 127 } } pub(super) fn allocate(&mut self, size: vk::DeviceSize, alignment: vk::DeviceSize) -> (StagingAllocation, StagingAllocationId) { if let Some((alloc, slot_id)) = self.current_buffer.try_allocate(size, alignment) { (alloc, StagingAllocationId{ buffer_id: self.current_buffer_id, slot_id }) } else { self.create_new_buffer(size); let (alloc, slot_id) = self.current_buffer.try_allocate(size, alignment).unwrap(); (alloc, StagingAllocationId{ buffer_id: 0, slot_id }) } } pub(super) fn free(&mut self, allocation: StagingAllocationId) { if allocation.buffer_id == self.current_buffer_id { self.current_buffer.free(allocation.slot_id); } else { let mut delete = None; for (index, (id, buffer)) in self.old_buffers.iter_mut().enumerate() { if *id == allocation.buffer_id { buffer.free(allocation.slot_id); if buffer.is_empty() { delete = Some(index); } break; } } if let Some(index) = delete { self.old_buffers.swap_remove(index); } } } fn create_new_buffer(&mut self, additional_size: vk::DeviceSize) { let mut usage_sum = self.current_buffer.used_byte_count(); for (_, old) in &self.old_buffers { usage_sum += old.used_byte_count(); } usage_sum += additional_size; let new_size = usage_sum + ((usage_sum * (self.over_allocation as u64)) / (u8::MAX as u64)); let new_size = std::cmp::max(new_size, Self::MIN_BUFFER_SIZE); // Yes this is slow but it shouldn't matter since we never have many buffers while self.is_id_unused(self.next_buffer_id) { // Technically there is a potential infinite loop here but at that point we would have // allocated at least 1TB of memory so i will accept this risk self.next_buffer_id = self.next_buffer_id.wrapping_add(1); } let id = self.next_buffer_id; self.next_buffer_id = self.next_buffer_id.wrapping_add(1); let buffer = StagingBuffer::new(self.device.clone(), new_size); let old = std::mem::replace(&mut self.current_buffer, buffer); self.old_buffers.push((self.current_buffer_id, old)); self.current_buffer_id = id; } fn is_id_unused(&self, id: u16) -> bool { if id == self.current_buffer_id { return false; } for (old, _) in &self.old_buffers { if *old == id { return false; } } true } } struct StagingBuffer { device: Arc, buffer: vk::Buffer, mapped_ptr: NonNull, allocation: Allocation, allocator: RingAllocator, } impl StagingBuffer { fn new(device: Arc, size: vk::DeviceSize) -> Self { let info = vk::BufferCreateInfo::builder() .size(size) .usage(vk::BufferUsageFlags::TRANSFER_SRC | vk::BufferUsageFlags::TRANSFER_DST) .sharing_mode(vk::SharingMode::EXCLUSIVE); let (buffer, allocation, mapped_ptr) = unsafe { device.get_allocator().create_buffer(&info, HostAccess::Random, &format_args!("StagingBuffer")) }.unwrap(); Self { device, buffer, mapped_ptr: mapped_ptr.unwrap(), allocation, allocator: RingAllocator::new(size) } } fn try_allocate(&mut self, size: vk::DeviceSize, alignment: vk::DeviceSize) -> Option<(StagingAllocation, u16)> { self.allocator.allocate(size, alignment).map(|(offset, slot)| { let alloc = StagingAllocation { buffer: self.buffer, offset, mapped: unsafe { NonNull::new_unchecked(self.mapped_ptr.as_ptr().offset(offset as isize)) } }; (alloc, slot) }) } fn free(&mut self, slot_id: u16) { self.allocator.free(slot_id); } fn is_empty(&self) -> bool { self.allocator.is_empty() } fn used_byte_count(&self) -> vk::DeviceSize { self.allocator.used_byte_count() } } impl Drop for StagingBuffer { fn drop(&mut self) { if !self.allocator.is_empty() { log::warn!("Destroying staging buffer with life allocations!"); } unsafe { self.device.get_allocator().destroy_buffer(self.buffer, self.allocation) }; } } unsafe impl Send for StagingBuffer { // Needed because of NonNull } unsafe impl Sync for StagingBuffer { // Needed because of NonNull } pub(super) struct StagingAllocation { pub(super) buffer: vk::Buffer, pub(super) offset: vk::DeviceSize, pub(super) mapped: NonNull, } unsafe impl Send for StagingAllocation { // Needed because of NonNull } unsafe impl Sync for StagingAllocation { // Needed because of NonNull } ================================================ FILE: core/natives/src/renderer/emulator/worker.rs ================================================ use std::cell::RefCell; use std::collections::HashMap; use std::marker::PhantomData; use std::rc::Rc; use std::sync::Arc; use std::time::Duration; use ash::prelude::VkResult; use ash::vk; use bumpalo::Bump; use crate::device::device::Queue; use crate::renderer::emulator::pass::PassId; use crate::renderer::emulator::immediate::ImmediateBuffer; use crate::renderer::emulator::pipeline::{EmulatorOutput, EmulatorPipeline, EmulatorPipelinePass, PipelineTask}; use crate::prelude::*; use crate::renderer::emulator::global_objects::{GlobalImage, GlobalMesh}; use crate::renderer::emulator::mc_shaders::ShaderId; use crate::renderer::emulator::share::{NextTaskResult, Share}; use crate::renderer::emulator::staging::StagingAllocationId; pub(super) enum WorkerTask { StartPass(PassId, Arc, Box, Arc, vk::Sampler), EndPass(Box), UseGlobalMesh(Arc), UseGlobalImage(Arc), UseShader(ShaderId), UseOutput(Box), PipelineTask(PipelineTask), WriteGlobalMesh(GlobalMeshWrite, bool), ClearGlobalImage(GlobalImageClear, bool), WriteGlobalImage(GlobalImageWrite), GenerateGlobalImageMipmaps(Arc, PassId), } pub(super) struct GlobalMeshWrite { pub(super) after_pass: PassId, pub(super) staging_allocation: StagingAllocationId, pub(super) staging_range: (vk::DeviceSize, vk::DeviceSize), pub(super) staging_buffer: vk::Buffer, pub(super) dst_mesh: Arc, pub(super) regions: Box<[vk::BufferCopy]>, } pub(super) struct GlobalImageWrite { pub(super) after_pass: PassId, pub(super) staging_allocation: StagingAllocationId, pub(super) staging_range: (vk::DeviceSize, vk::DeviceSize), pub(super) staging_buffer: vk::Buffer, pub(super) dst_image: Arc, pub(super) regions: Box<[vk::BufferImageCopy]>, } pub(super) struct GlobalImageClear { pub(super) after_pass: PassId, pub(super) clear_value: vk::ClearColorValue, pub(super) dst_image: Arc, } pub(super) fn run_worker(device: Arc, share: Arc) { let queue = device.get_main_queue(); let pool = Rc::new(RefCell::new(WorkerObjectPool::new(device.clone(), queue.get_queue_family_index()))); let mut current_pass: Option = None; let mut old_frames = Vec::new(); // A global objects recorder submitted before the current frame. // If no active pass exits this **must** be [`None`]. let mut current_global_recorder: Option = None; // A global objects recorder submitted before the next frame. // When a pass is started this object is moved to `current_global_recorder`. let mut next_global_recorder: Option = None; let queue = device.get_main_queue(); loop { old_frames.retain(|old: &PassState| { !old.is_complete() }); let task = match share.try_get_next_task_timeout(Duration::from_micros(500)) { NextTaskResult::Ok(task) => task, NextTaskResult::Timeout => continue, }; match task { WorkerTask::StartPass(id, pipeline, pass, placeholder_image, placeholder_sampler) => { if current_pass.is_some() { log::error!("Worker received WorkerTask::StartPass when a pass is already running"); panic!() } let state = PassState::new(id, pipeline, pass, device.clone(), &queue, share.clone(), pool.clone(), placeholder_image, placeholder_sampler); current_pass = Some(state); current_global_recorder = next_global_recorder.take(); } WorkerTask::EndPass(immediate_buffer) => { if let Some(mut pass) = current_pass.take() { pass.use_immediate_buffer(immediate_buffer); pass.submit(&queue, current_global_recorder.take()); old_frames.push(pass); } else { log::error!("Worker received WorkerTask::EndPass when no active pass exists"); panic!() } } WorkerTask::UseGlobalMesh(mesh) => { if let Some(pass) = &mut current_pass { pass.global_meshes.push(mesh) } else { log::error!("Worker received WorkerTask::UseStaticMesh when no active pass exists"); panic!() } } WorkerTask::UseGlobalImage(image) => { if let Some(pass) = &mut current_pass { pass.global_images.push(image); } else { log::error!("Worker received WorkerTask::UseStaticImage when no active pass exits"); panic!() } } WorkerTask::UseShader(shader) => { if let Some(pass) = &mut current_pass { pass.shaders.push(shader); } else { log::error!("Worker received WorkerTask::UseShader when no active pass exists"); panic!() } } WorkerTask::UseOutput(output) => { if let Some(pass) = &mut current_pass { pass.use_output(output); } else { log::error!("Worker received WorkerTask::UseOutput when no active pass exists"); panic!() } } WorkerTask::PipelineTask(task) => { if let Some(pass) = &mut current_pass { pass.process_task(&task) } else { log::error!("Worker received WorkerTask::PipelineTask when no active pass exists"); panic!() } } WorkerTask::WriteGlobalMesh(write, uninit) => { if let Some(current_pass) = ¤t_pass { if current_pass.pass_id > write.after_pass { get_or_create_recorder(&mut current_global_recorder, &share, &pool).record_global_buffer_write(write, uninit); } else { get_or_create_recorder(&mut next_global_recorder, &share, &pool).record_global_buffer_write(write, uninit); } } else { get_or_create_recorder(&mut next_global_recorder, &share, &pool).record_global_buffer_write(write, uninit); } } WorkerTask::ClearGlobalImage(clear, uninit) => { if let Some(current_pass) = ¤t_pass { if current_pass.pass_id > clear.after_pass { get_or_create_recorder(&mut current_global_recorder, &share, &pool).record_global_image_clear(clear, uninit); } else { get_or_create_recorder(&mut next_global_recorder, &share, &pool).record_global_image_clear(clear, uninit); } } else { get_or_create_recorder(&mut next_global_recorder, &share, &pool).record_global_image_clear(clear, uninit); } } WorkerTask::WriteGlobalImage(write) => { if let Some(current_pass) = ¤t_pass { if current_pass.pass_id > write.after_pass { get_or_create_recorder(&mut current_global_recorder, &share, &pool).record_global_image_write(write, false); } else { get_or_create_recorder(&mut next_global_recorder, &share, &pool).record_global_image_write(write, false); } } else { get_or_create_recorder(&mut next_global_recorder, &share, &pool).record_global_image_write(write, false); } } WorkerTask::GenerateGlobalImageMipmaps(image, after_pass) => { if let Some(current_pass) = ¤t_pass { if current_pass.pass_id > after_pass { get_or_create_recorder(&mut current_global_recorder, &share, &pool).record_global_image_generate_mipmaps(image); } else { get_or_create_recorder(&mut next_global_recorder, &share, &pool).record_global_image_generate_mipmaps(image); } } else { get_or_create_recorder(&mut next_global_recorder, &share, &pool).record_global_image_generate_mipmaps(image); } } } } } fn get_or_create_recorder<'a>(recorder: &'a mut Option, share: &Arc, object_pool: &Rc>) -> &'a mut GlobalObjectsRecorder { if let Some(recorder) = recorder { recorder } else { *recorder = Some(GlobalObjectsRecorder::new(share.clone(), object_pool.clone())); recorder.as_mut().unwrap() } } struct WorkerObjectPool { device: Arc, command_pool: vk::CommandPool, command_buffers: Vec, fences: Vec, } impl WorkerObjectPool { fn new(device: Arc, queue_family: u32) -> Self { let info = vk::CommandPoolCreateInfo::builder() .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER | vk::CommandPoolCreateFlags::TRANSIENT) .queue_family_index(queue_family); let command_pool = unsafe { device.vk().create_command_pool(&info, None) }.unwrap(); Self { device, command_pool, command_buffers: Vec::new(), fences: Vec::new(), } } fn get_buffer(&mut self) -> vk::CommandBuffer { if self.command_buffers.is_empty() { let info = vk::CommandBufferAllocateInfo::builder() .command_pool(self.command_pool) .level(vk::CommandBufferLevel::PRIMARY) .command_buffer_count(8); let buffers = unsafe { self.device.vk().allocate_command_buffers(&info) }.unwrap(); self.command_buffers.extend(buffers); } self.command_buffers.pop().unwrap() } fn return_buffer(&mut self, buffer: vk::CommandBuffer) { self.command_buffers.push(buffer) } fn return_buffers(&mut self, buffers: &[vk::CommandBuffer]) { self.command_buffers.extend_from_slice(buffers); } fn get_fence(&mut self) -> vk::Fence { if self.fences.is_empty() { let info = vk::FenceCreateInfo::builder(); let fence = unsafe { self.device.vk().create_fence(&info, None) }.unwrap(); return fence; } self.fences.pop().unwrap() } fn return_fence(&mut self, fence: vk::Fence) { self.fences.push(fence); } } pub struct PooledObjectProvider { share: Arc, pool: Rc>, used_buffers: Vec, used_fences: Vec, } impl PooledObjectProvider { fn new(share: Arc, pool: Rc>) -> Self { Self { share, pool, used_buffers: Vec::with_capacity(8), used_fences: Vec::with_capacity(4), } } pub fn get_command_buffer(&mut self) -> vk::CommandBuffer { let buffer = self.pool.borrow_mut().get_buffer(); self.used_buffers.push(buffer); buffer } pub fn get_begin_command_buffer(&mut self) -> VkResult { let cmd = self.get_command_buffer(); let info = vk::CommandBufferBeginInfo::builder() .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT); unsafe { self.pool.borrow().device.vk().begin_command_buffer(cmd, &info) }?; Ok(cmd) } pub fn get_fence(&mut self) -> vk::Fence { let fence = self.pool.borrow_mut().get_fence(); self.used_fences.push(fence); fence } pub fn allocate_uniform(&mut self, data: &[u8]) -> (vk::Buffer, vk::DeviceSize) { self.share.allocate_uniform(data) } } impl Drop for PooledObjectProvider { fn drop(&mut self) { self.pool.borrow_mut().return_buffers(self.used_buffers.as_slice()); } } pub struct SubmitRecorder<'a> { submits: Vec, _phantom: PhantomData<&'a ()>, } impl<'a> SubmitRecorder<'a> { fn new(capacity: usize) -> Self { Self { submits: Vec::with_capacity(capacity), _phantom: PhantomData, } } pub fn push(&mut self, submit: vk::SubmitInfo2Builder<'a>) { self.submits.push(submit.build()); } fn as_slice(&self) -> &[vk::SubmitInfo2] { self.submits.as_slice() } } struct PassState { share: Arc, device: Arc, object_pool: PooledObjectProvider, pass_id: PassId, pipeline: Arc, pass: Box, outputs: Vec>, immediate_buffer: Option>, global_meshes: Vec>, global_images: Vec>, shaders: Vec, pre_cmd: vk::CommandBuffer, post_cmd: vk::CommandBuffer, end_fence: Option, gob: Option, } impl PassState { fn new( pass_id: PassId, pipeline: Arc, mut pass: Box, device: Arc, queue: &Queue, share: Arc, pool: Rc>, placeholder_image: Arc, placeholder_sampler: vk::Sampler ) -> Self { let mut object_pool = PooledObjectProvider::new(share.clone(), pool); let pre_cmd = object_pool.get_begin_command_buffer().unwrap(); let post_cmd = object_pool.get_begin_command_buffer().unwrap(); pass.init(queue, &mut object_pool, placeholder_image.get_sampler_view(), placeholder_sampler); Self { share, device, object_pool, pass_id, pipeline, pass, outputs: Vec::with_capacity(8), immediate_buffer: None, global_meshes: Vec::new(), global_images: vec![placeholder_image], shaders: Vec::new(), pre_cmd, post_cmd, end_fence: None, gob: None } } fn use_immediate_buffer(&mut self, immediate_buffer: Box) { if self.immediate_buffer.is_some() { log::error!("Called PassState::use_immediate_buffer when a immediate buffer already exists"); panic!() } immediate_buffer.generate_copy_commands(self.pre_cmd); self.immediate_buffer = Some(immediate_buffer); } fn use_output(&mut self, mut output: Box) { output.init(self.pass.as_ref(), &mut self.object_pool); self.outputs.push(output); } fn process_task(&mut self, task: &PipelineTask) { self.pass.process_task(task, &mut self.object_pool); } fn submit(&mut self, queue: &Queue, gob: Option) { assert!(self.end_fence.is_none()); let end_fence = self.object_pool.get_fence(); self.end_fence = Some(end_fence); unsafe { self.device.vk().end_command_buffer(self.pre_cmd) }.unwrap(); unsafe { self.device.vk().end_command_buffer(self.post_cmd) }.unwrap(); let submit_alloc = Bump::new(); let mut submit_recorder = SubmitRecorder::new(32); if let Some(mut gob) = gob { gob.record(&mut submit_recorder, &submit_alloc); self.gob = Some(gob); } self.record_pre_submits(&mut submit_recorder, &submit_alloc); self.pass.record(&mut self.object_pool, &mut submit_recorder, &submit_alloc); for output in &mut self.outputs { output.record(&mut self.object_pool, &mut submit_recorder, &submit_alloc); } self.record_post_submits(&mut submit_recorder, &submit_alloc); unsafe { queue.submit_2(submit_recorder.as_slice(), Some(end_fence)) }.unwrap(); for output in &mut self.outputs { output.on_post_submit(&queue); } } fn is_complete(&self) -> bool { if let Some(fence) = self.end_fence { unsafe { self.device.vk().get_fence_status(fence) }.unwrap() } else { panic!("Illegal state"); } } fn record_pre_submits<'a>(&self, recorder: &mut SubmitRecorder<'a>, alloc: &'a Bump) { let cmd_infos = alloc.alloc([ vk::CommandBufferSubmitInfo::builder() .command_buffer(self.pre_cmd) .build() ]); let submit_info = vk::SubmitInfo2::builder() .command_buffer_infos(cmd_infos); recorder.push(submit_info); } fn record_post_submits<'a>(&self, _: &mut SubmitRecorder<'a>, _: &'a Bump) { } } impl Drop for PassState { fn drop(&mut self) { if let Some(immediate_buffer) = self.immediate_buffer.take() { self.share.return_immediate_buffer(immediate_buffer); } for shader in &self.shaders { self.pipeline.dec_shader_used(*shader); } } } struct GlobalObjectsRecorder { share: Arc, _object_pool: PooledObjectProvider, cmd: vk::CommandBuffer, staging_allocations: Vec, staging_barriers: Vec, used_global_meshes: HashMap, gob::MeshState>, used_global_images: HashMap, gob::ImageState>, /// A [`vk::ImageMemoryBarrier2`] Vec which can be used locally inside functions to avoid new /// allocations. It should always be cleared before use. tmp_image_barriers: Vec, /// A [`vk::BufferMemoryBarrier2`] Vec which can be used locally inside functions to avoid new /// allocations. It should always be cleared before use. tmp_buffer_barriers: Vec, } impl GlobalObjectsRecorder { fn new(share: Arc, object_pool: Rc>) -> Self { let mut object_pool = PooledObjectProvider::new(share.clone(), object_pool); let cmd = object_pool.get_begin_command_buffer().unwrap_or_else(|err| { log::error!("Failed to begin global object command buffer {:?}", err); panic!(); }); Self { share, _object_pool: object_pool, cmd, staging_allocations: Vec::new(), staging_barriers: Vec::new(), used_global_meshes: HashMap::new(), used_global_images: HashMap::new(), tmp_image_barriers: Vec::new(), tmp_buffer_barriers: Vec::new(), } } fn record_global_buffer_write(&mut self, write: GlobalMeshWrite, is_uninit: bool) { let dst_buffer = write.dst_mesh.get_buffer_handle(); if !write.regions.is_empty() { self.transition_mesh(write.dst_mesh, gob::MeshState::TransferWrite, is_uninit); unsafe { self.share.get_device().vk().cmd_copy_buffer( self.cmd, write.staging_buffer, dst_buffer, write.regions.as_ref() ); } } self.push_staging(write.staging_allocation, write.staging_buffer, write.staging_range.0, write.staging_range.1); } fn record_global_image_clear(&mut self, clear: GlobalImageClear, is_uninit: bool) { let dst_image = clear.dst_image.get_image_handle(); self.transition_image(clear.dst_image, gob::ImageState::TransferWrite, is_uninit); unsafe { self.share.get_device().vk().cmd_clear_color_image( self.cmd, dst_image, vk::ImageLayout::TRANSFER_DST_OPTIMAL, &clear.clear_value, std::slice::from_ref(&vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, level_count: vk::REMAINING_MIP_LEVELS, base_array_layer: 0, layer_count: vk::REMAINING_ARRAY_LAYERS }) ) } } fn record_global_image_write(&mut self, write: GlobalImageWrite, is_uninit: bool) { let dst_image = write.dst_image.get_image_handle(); self.transition_image(write.dst_image, gob::ImageState::TransferWrite, is_uninit); if !write.regions.is_empty() { unsafe { self.share.get_device().vk().cmd_copy_buffer_to_image( self.cmd, write.staging_buffer, dst_image, vk::ImageLayout::TRANSFER_DST_OPTIMAL, write.regions.as_ref() ); } } self.push_staging(write.staging_allocation, write.staging_buffer, write.staging_range.0, write.staging_range.1); } fn record_global_image_generate_mipmaps(&mut self, image: Arc) { let mip_levels = image.get_mip_levels(); if mip_levels > 1 { let handle = image.get_image_handle(); let src_size = image.get_size(); let mut src_size = Vec2i32::new(src_size[0] as i32, src_size[1] as i32); self.transition_image(image, gob::ImageState::GenerateMipmaps, false); let device = self.share.get_device(); for level in 1..mip_levels { if level > 1 { let barrier = vk::ImageMemoryBarrier2::builder() .src_stage_mask(vk::PipelineStageFlags2::TRANSFER) .src_access_mask(vk::AccessFlags2::TRANSFER_WRITE) .dst_stage_mask(vk::PipelineStageFlags2::TRANSFER) .dst_access_mask(vk::AccessFlags2::TRANSFER_READ) .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) .new_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL) .image(handle) .subresource_range(vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: level - 1, level_count: 1, base_array_layer: 0, layer_count: 0 }); let info = vk::DependencyInfo::builder() .image_memory_barriers(std::slice::from_ref(&barrier)); unsafe { device.synchronization_2_khr().cmd_pipeline_barrier2(self.cmd, &info); } } let dst_size = Vec2i32::new( if src_size[0] > 1 { src_size[0] / 2 } else { 1 }, if src_size[1] > 1 { src_size[1] / 2 } else { 1 } ); let blit = vk::ImageBlit::builder() .src_subresource(vk::ImageSubresourceLayers { aspect_mask: vk::ImageAspectFlags::COLOR, mip_level: level - 1, base_array_layer: 0, layer_count: 1 }) .src_offsets([vk::Offset3D { x: 0, y: 0, z: 0 }, vk::Offset3D { x: src_size[0], y: src_size[1], z: 1 }]) .dst_subresource(vk::ImageSubresourceLayers { aspect_mask: vk::ImageAspectFlags::COLOR, mip_level: level, base_array_layer: 0, layer_count: 1 }) .dst_offsets([vk::Offset3D { x: 0, y: 0, z: 0 }, vk::Offset3D { x: dst_size[0], y: dst_size[1], z: 1 }]); unsafe { device.vk().cmd_blit_image( self.cmd, handle, vk::ImageLayout::TRANSFER_SRC_OPTIMAL, handle, vk::ImageLayout::TRANSFER_DST_OPTIMAL, std::slice::from_ref(&blit), vk::Filter::LINEAR ); } src_size = dst_size; } } } fn record<'a>(&mut self, recorder: &mut SubmitRecorder<'a>, bump: &'a Bump) { let buffer_post_barriers = self.generate_buffer_post_barriers(); let image_post_barriers = self.generate_image_post_barriers(); let device = self.share.get_device(); if !buffer_post_barriers.is_empty() || !image_post_barriers.is_empty() { let buffer_post_barriers = buffer_post_barriers.as_slice(); let image_post_barriers = image_post_barriers.as_slice(); // If we have too many barriers in a single command the driver may fail to record (Yes this limit has been hit at 4000 barriers during testing in minecraft) const CHUNK_SIZE: usize = 256; let chunk_count = std::cmp::max((buffer_post_barriers.len() / CHUNK_SIZE) + 1, (image_post_barriers.len() / CHUNK_SIZE) + 1); for chunk in 0..chunk_count { let min = chunk * CHUNK_SIZE; let max = min + CHUNK_SIZE; let mut info = vk::DependencyInfo::builder(); if min < buffer_post_barriers.len() { let max = std::cmp::min(max, buffer_post_barriers.len()); info = info.buffer_memory_barriers(&buffer_post_barriers[min..max]); } if min < image_post_barriers.len() { let max = std::cmp::min(max, image_post_barriers.len()); info = info.image_memory_barriers(&image_post_barriers[min..max]); } unsafe { device.synchronization_2_khr().cmd_pipeline_barrier2(self.cmd, &info); } } } unsafe { device.vk().end_command_buffer(self.cmd) }.unwrap_or_else(|err| { log::error!("Failed to end global objects command buffer recording {:?}", err); panic!() }); let cmd_info = bump.alloc(vk::CommandBufferSubmitInfo::builder() .command_buffer(self.cmd) .build() ); recorder.push(vk::SubmitInfo2::builder() .command_buffer_infos(std::slice::from_ref(cmd_info)) ); } fn generate_buffer_post_barriers(&mut self) -> Vec { let mut barriers = std::mem::replace(&mut self.staging_barriers, Vec::new()); for (mesh, old_state) in &self.used_global_meshes { let handle = mesh.get_buffer_handle(); gob::generate_mesh_barriers(*old_state, gob::MeshState::Ready, handle, &mut barriers); } barriers } fn generate_image_post_barriers(&mut self) -> Vec { let mut barriers: Vec = Vec::new(); for (image, old_state) in &self.used_global_images { let handle = image.get_image_handle(); let mip_levels = image.get_mip_levels(); gob::generate_image_barriers(*old_state, gob::ImageState::Ready, handle, mip_levels, &mut barriers); } barriers } fn push_staging(&mut self, alloc: StagingAllocationId, buffer: vk::Buffer, offset: vk::DeviceSize, size: vk::DeviceSize) { self.staging_allocations.push(alloc); let barrier = vk::BufferMemoryBarrier2::builder() .src_stage_mask(vk::PipelineStageFlags2::TRANSFER) .src_access_mask(vk::AccessFlags2::TRANSFER_READ) .dst_stage_mask(vk::PipelineStageFlags2::HOST) .dst_access_mask(vk::AccessFlags2::HOST_WRITE) .buffer(buffer) .offset(offset) .size(size); let info = vk::DependencyInfo::builder() .buffer_memory_barriers(std::slice::from_ref(&barrier)); unsafe { self.share.get_device().synchronization_2_khr().cmd_pipeline_barrier2(self.cmd, &info) }; } /// Transitions a mesh to a new state and adds it to the used mesh list. /// /// If the mesh is not in the used mesh list the mesh is currently either uninitialized or /// ready. In that case if maybe_uninit is set the mesh is assumed to be uninitialized otherwise /// it is assumed to be in the ready state. fn transition_mesh(&mut self, mesh: Arc, new_state: gob::MeshState, maybe_uninit: bool) { let handle = mesh.get_buffer_handle(); let old_state = self.used_global_meshes.insert(mesh, new_state).unwrap_or_else(|| { if maybe_uninit { gob::MeshState::Uninitialized } else { gob::MeshState::Ready } }); self.tmp_buffer_barriers.clear(); gob::generate_mesh_barriers(old_state, new_state, handle, &mut self.tmp_buffer_barriers); if !self.tmp_buffer_barriers.is_empty() { let info = vk::DependencyInfo::builder() .buffer_memory_barriers(self.tmp_buffer_barriers.as_slice()); unsafe { self.share.get_device().synchronization_2_khr().cmd_pipeline_barrier2(self.cmd, &info); } } } /// Transitions a image to a new state and adds it to the used image list. /// /// If the image is not in the used image list the image is currently either uninitialized or /// ready. In that case if maybe_uninit is set the image is assumed to be uninitialized otherwise /// it is assumed to be in the ready state. fn transition_image(&mut self, image: Arc, new_state: gob::ImageState, maybe_uninit: bool) { let handle = image.get_image_handle(); let mip_levels = image.get_mip_levels(); let old_state = self.used_global_images.insert(image, new_state).unwrap_or_else(|| { if maybe_uninit { gob::ImageState::Uninitialized } else { gob::ImageState::Ready } }); self.tmp_image_barriers.clear(); gob::generate_image_barriers(old_state, new_state, handle, mip_levels, &mut self.tmp_image_barriers); if !self.tmp_image_barriers.is_empty() { let info = vk::DependencyInfo::builder() .image_memory_barriers(self.tmp_image_barriers.as_slice()); unsafe { self.share.get_device().synchronization_2_khr().cmd_pipeline_barrier2(self.cmd, &info); } } } } impl Drop for GlobalObjectsRecorder { fn drop(&mut self) { let mut guard = self.share.get_staging_pool().lock().unwrap_or_else(|_| { log::error!("Poisoned staging memory mutex in GlobalObjectsRecorder::drop"); panic!(); }); for allocation in std::mem::replace(&mut self.staging_allocations, Vec::new()) { guard.free(allocation); } } } mod gob { //! Utility functions to create barriers for global objects use ash::vk; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub(super) enum MeshState { /// Mesh has not been initialized yet Uninitialized, /// Mesh is ready to be used for rendering Ready, /// Mesh was previously written to TransferWrite, } pub(super) fn generate_mesh_barriers(old_state: MeshState, new_state: MeshState, buffer: vk::Buffer, barriers: &mut Vec) { match (old_state, new_state) { (MeshState::Uninitialized, _) => { }, (old, MeshState::Uninitialized) => { log::error!("Mesh cannot be transitioned into uninitialized (was {:?})", old); panic!(); }, (MeshState::Ready, MeshState::Ready) => { log::warn!("Transitioned mesh from ready to ready. Why?"); } (old, new) => { let mut barrier = vk::BufferMemoryBarrier2::builder() .buffer(buffer) .offset(0) .size(vk::WHOLE_SIZE); barrier = match old { MeshState::Uninitialized => panic!(), // Impossible MeshState::Ready => MESH_READY_INFO().write_src(barrier), MeshState::TransferWrite => MESH_TRANSFER_WRITE_INFO.write_src(barrier) }; barrier = match new { MeshState::Uninitialized => panic!(), // Impossible MeshState::Ready => MESH_READY_INFO().write_dst(barrier), MeshState::TransferWrite => MESH_TRANSFER_WRITE_INFO.write_dst(barrier) }; barriers.push(barrier.build()); } } } // This needs to be a function because of the bitor. Waiting for const impl #[allow(non_snake_case)] fn MESH_READY_INFO() -> BufferAccessInfo { BufferAccessInfo::new(vk::PipelineStageFlags2::VERTEX_INPUT, vk::AccessFlags2::VERTEX_ATTRIBUTE_READ | vk::AccessFlags2::INDEX_READ) } const MESH_TRANSFER_WRITE_INFO: BufferAccessInfo = BufferAccessInfo::new(vk::PipelineStageFlags2::TRANSFER, vk::AccessFlags2::TRANSFER_WRITE); struct BufferAccessInfo { stage_mask: vk::PipelineStageFlags2, access_mask: vk::AccessFlags2, } impl BufferAccessInfo { #[inline] const fn new(stage_mask: vk::PipelineStageFlags2, access_mask: vk::AccessFlags2) -> Self { Self { stage_mask, access_mask } } #[inline] fn write_src<'a>(&self, barrier: vk::BufferMemoryBarrier2Builder<'a>) -> vk::BufferMemoryBarrier2Builder<'a> { barrier .src_stage_mask(self.stage_mask) .src_access_mask(self.access_mask) } #[inline] fn write_dst<'a>(&self, barrier: vk::BufferMemoryBarrier2Builder<'a>) -> vk::BufferMemoryBarrier2Builder<'a> { barrier .dst_stage_mask(self.stage_mask) .dst_access_mask(self.access_mask) } } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub(super) enum ImageState { /// Image has not been initialized yet Uninitialized, /// Image is ready to be used for rendering Ready, /// Image was previously written to TransferWrite, /// Image had previously generated its mipmaps GenerateMipmaps, } pub(super) fn generate_image_barriers(old_state: ImageState, new_state: ImageState, image: vk::Image, mip_levels: u32, barriers: &mut Vec) { match (old_state, new_state) { (ImageState::Uninitialized, ImageState::TransferWrite) => { let mut barrier = vk::ImageMemoryBarrier2::builder() .image(image) .subresource_range(make_full_subresource_range(vk::ImageAspectFlags::COLOR)); barrier = IMAGE_UNINITIALIZED_INFO.write_src(barrier); barrier = IMAGE_TRANSFER_WRITE_INFO.write_dst(barrier); barriers.push(barrier.build()); } (ImageState::Ready, ImageState::TransferWrite) => { let mut barrier = vk::ImageMemoryBarrier2::builder() .image(image) .subresource_range(make_full_subresource_range(vk::ImageAspectFlags::COLOR)); barrier = IMAGE_READY_INFO.write_src(barrier); barrier = IMAGE_TRANSFER_WRITE_INFO.write_dst(barrier); barriers.push(barrier.build()); } (ImageState::Ready, ImageState::GenerateMipmaps) => { let mut barrier0 = vk::ImageMemoryBarrier2::builder() .image(image) .subresource_range(make_first_mip_subresource_range(vk::ImageAspectFlags::COLOR)); barrier0 = IMAGE_READY_INFO.write_src(barrier0); barrier0 = IMAGE_GENERATE_MIPMAPS_0_INFO.write_dst(barrier0); barriers.push(barrier0.build()); let mut barrier1 = vk::ImageMemoryBarrier2::builder() .image(image) .subresource_range(make_exclude_first_mips_subresource_range(vk::ImageAspectFlags::COLOR)); barrier1 = IMAGE_READY_INFO.write_src(barrier1); barrier1 = IMAGE_GENERATE_MIPMAPS_1_INFO.write_dst(barrier1); barriers.push(barrier1.build()); } (ImageState::TransferWrite, ImageState::Ready) => { let mut barrier = vk::ImageMemoryBarrier2::builder() .image(image) .subresource_range(make_full_subresource_range(vk::ImageAspectFlags::COLOR)); barrier = IMAGE_TRANSFER_WRITE_INFO.write_src(barrier); barrier = IMAGE_READY_INFO.write_dst(barrier); barriers.push(barrier.build()); } (ImageState::TransferWrite, ImageState::TransferWrite) => { let mut barrier = vk::ImageMemoryBarrier2::builder() .image(image) .subresource_range(make_full_subresource_range(vk::ImageAspectFlags::COLOR)); barrier = IMAGE_TRANSFER_WRITE_INFO.write_src(barrier); barrier = IMAGE_TRANSFER_WRITE_INFO.write_dst(barrier); barriers.push(barrier.build()); } (ImageState::TransferWrite, ImageState::GenerateMipmaps) => { let mut barrier0 = vk::ImageMemoryBarrier2::builder() .image(image) .subresource_range(make_first_mip_subresource_range(vk::ImageAspectFlags::COLOR)); barrier0 = IMAGE_TRANSFER_WRITE_INFO.write_src(barrier0); barrier0 = IMAGE_GENERATE_MIPMAPS_0_INFO.write_dst(barrier0); barriers.push(barrier0.build()); let mut barrier1 = vk::ImageMemoryBarrier2::builder() .image(image) .subresource_range(make_exclude_first_mips_subresource_range(vk::ImageAspectFlags::COLOR)); barrier1 = IMAGE_TRANSFER_WRITE_INFO.write_src(barrier1); barrier1 = IMAGE_GENERATE_MIPMAPS_1_INFO.write_dst(barrier1); barriers.push(barrier1.build()); } (ImageState::GenerateMipmaps, ImageState::Ready) => { let mut barrier0 = vk::ImageMemoryBarrier2::builder() .image(image) .subresource_range(make_exclude_last_mips_subresource_range(vk::ImageAspectFlags::COLOR, mip_levels)); barrier0 = IMAGE_GENERATE_MIPMAPS_0_INFO.write_src(barrier0); barrier0 = IMAGE_READY_INFO.write_dst(barrier0); barriers.push(barrier0.build()); let mut barrier1 = vk::ImageMemoryBarrier2::builder() .image(image) .subresource_range(make_last_mip_subresource_range(vk::ImageAspectFlags::COLOR, mip_levels)); barrier1 = IMAGE_GENERATE_MIPMAPS_1_INFO.write_src(barrier1); barrier1 = IMAGE_READY_INFO.write_dst(barrier1); barriers.push(barrier1.build()); } (ImageState::GenerateMipmaps, ImageState::TransferWrite) => { let mut barrier0 = vk::ImageMemoryBarrier2::builder() .image(image) .subresource_range(make_exclude_last_mips_subresource_range(vk::ImageAspectFlags::COLOR, mip_levels)); barrier0 = IMAGE_GENERATE_MIPMAPS_0_INFO.write_src(barrier0); barrier0 = IMAGE_TRANSFER_WRITE_INFO.write_dst(barrier0); barriers.push(barrier0.build()); let mut barrier1 = vk::ImageMemoryBarrier2::builder() .image(image) .subresource_range(make_last_mip_subresource_range(vk::ImageAspectFlags::COLOR, mip_levels)); barrier1 = IMAGE_GENERATE_MIPMAPS_1_INFO.write_src(barrier1); barrier1 = IMAGE_TRANSFER_WRITE_INFO.write_dst(barrier1); barriers.push(barrier1.build()); } (ImageState::Ready, ImageState::Ready) => { log::warn!("Transitioned image from ready to ready. Why?"); } (ImageState::Uninitialized, new) => { log::error!("Image cannot be transitioned from uninitialized to {:?}", new); panic!(); } (old, ImageState::Uninitialized) => { log::error!("Image cannot be transitioned into uninitialized (was {:?})", old); panic!(); } (ImageState::GenerateMipmaps, ImageState::GenerateMipmaps) => { log::error!("Image cannot be transitioned from generate mipmaps to generate mipmaps"); panic!(); } } } #[inline] fn make_full_subresource_range(aspect_mask: vk::ImageAspectFlags) -> vk::ImageSubresourceRange { vk::ImageSubresourceRange { aspect_mask, base_mip_level: 0, level_count: vk::REMAINING_MIP_LEVELS, base_array_layer: 0, layer_count: vk::REMAINING_ARRAY_LAYERS } } #[inline] fn make_exclude_last_mips_subresource_range(aspect_mask: vk::ImageAspectFlags, mip_levels: u32) -> vk::ImageSubresourceRange { vk::ImageSubresourceRange { aspect_mask, base_mip_level: 0, level_count: mip_levels - 1, base_array_layer: 0, layer_count: vk::REMAINING_ARRAY_LAYERS } } #[inline] fn make_last_mip_subresource_range(aspect_mask: vk::ImageAspectFlags, mip_levels: u32) -> vk::ImageSubresourceRange { vk::ImageSubresourceRange { aspect_mask, base_mip_level: mip_levels - 1, level_count: 1, base_array_layer: 0, layer_count: vk::REMAINING_ARRAY_LAYERS } } #[inline] fn make_exclude_first_mips_subresource_range(aspect_mask: vk::ImageAspectFlags) -> vk::ImageSubresourceRange { vk::ImageSubresourceRange { aspect_mask, base_mip_level: 1, level_count: vk::REMAINING_MIP_LEVELS, base_array_layer: 0, layer_count: vk::REMAINING_ARRAY_LAYERS, } } #[inline] fn make_first_mip_subresource_range(aspect_mask: vk::ImageAspectFlags) -> vk::ImageSubresourceRange { vk::ImageSubresourceRange { aspect_mask, base_mip_level: 0, level_count: 1, base_array_layer: 0, layer_count: vk::REMAINING_ARRAY_LAYERS, } } const IMAGE_UNINITIALIZED_INFO: ImageAccessInfo = ImageAccessInfo::new(vk::PipelineStageFlags2::NONE, vk::AccessFlags2::NONE, vk::ImageLayout::UNDEFINED); const IMAGE_READY_INFO: ImageAccessInfo = ImageAccessInfo::new(vk::PipelineStageFlags2::FRAGMENT_SHADER, vk::AccessFlags2::SHADER_SAMPLED_READ, vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL); const IMAGE_TRANSFER_WRITE_INFO: ImageAccessInfo = ImageAccessInfo::new(vk::PipelineStageFlags2::TRANSFER, vk::AccessFlags2::TRANSFER_WRITE, vk::ImageLayout::TRANSFER_DST_OPTIMAL); const IMAGE_GENERATE_MIPMAPS_0_INFO: ImageAccessInfo = ImageAccessInfo::new(vk::PipelineStageFlags2::TRANSFER, vk::AccessFlags2::TRANSFER_READ, vk::ImageLayout::TRANSFER_SRC_OPTIMAL); const IMAGE_GENERATE_MIPMAPS_1_INFO: ImageAccessInfo = ImageAccessInfo::new(vk::PipelineStageFlags2::TRANSFER, vk::AccessFlags2::TRANSFER_WRITE, vk::ImageLayout::TRANSFER_DST_OPTIMAL); struct ImageAccessInfo { stage_mask: vk::PipelineStageFlags2, access_mask: vk::AccessFlags2, layout: vk::ImageLayout, } impl ImageAccessInfo { #[inline] const fn new(stage_mask: vk::PipelineStageFlags2, access_mask: vk::AccessFlags2, layout: vk::ImageLayout) -> Self { Self { stage_mask, access_mask, layout } } #[inline] fn write_src<'a>(&self, barrier: vk::ImageMemoryBarrier2Builder<'a>) -> vk::ImageMemoryBarrier2Builder<'a> { barrier .src_stage_mask(self.stage_mask) .src_access_mask(self.access_mask) .old_layout(self.layout) } #[inline] fn write_dst<'a>(&self, barrier: vk::ImageMemoryBarrier2Builder<'a>) -> vk::ImageMemoryBarrier2Builder<'a> { barrier .dst_stage_mask(self.stage_mask) .dst_access_mask(self.access_mask) .new_layout(self.layout) } } } ================================================ FILE: core/natives/src/renderer/mod.rs ================================================ pub mod emulator; ================================================ FILE: core/natives/src/util/alloc.rs ================================================ //! Utilities to support creating device memory allocators use ash::vk; pub fn next_aligned(base: vk::DeviceSize, alignment: vk::DeviceSize) -> vk::DeviceSize { let rem = base % alignment; if rem == 0 { base } else { let diff = alignment - rem; base + diff } } pub struct RingAllocator { size: vk::DeviceSize, head: vk::DeviceSize, tail: vk::DeviceSize, used_bytes: vk::DeviceSize, alloc_list_head: Option, alloc_list_tail: Option, free_list: Option, slots: Vec, } impl RingAllocator { pub fn new(size: vk::DeviceSize) -> Self { let slots = vec![RingAllocatorSlot::new(Some(1)), RingAllocatorSlot::new(None)]; Self { size, head: 0, tail: 0, used_bytes: 0, alloc_list_head: None, alloc_list_tail: None, free_list: Some(0), slots, } } pub fn is_empty(&self) -> bool { self.alloc_list_head.is_none() } pub fn free_byte_count(&self) -> vk::DeviceSize { self.size - self.used_bytes } pub fn used_byte_count(&self) -> vk::DeviceSize { self.used_bytes } pub fn allocate(&mut self, size: u64, alignment: u64) -> Option<(vk::DeviceSize, u16)> { assert_ne!(alignment, 0u64); let next = next_aligned(self.head, alignment); let extra_used; let base; let end; if next + size > self.size { // Wraparound if size > self.tail { return None; // Out of memory } extra_used = (self.size - self.head) + size; base = 0; end = size; } else { // We need to test >= but only if we actually have allocations if self.alloc_list_head.is_some() && self.tail >= self.head && (next + size) > self.tail { return None; // Out of memory } base = next; end = next + size; extra_used = end - self.head; } if let Some(slot) = self.push_slot(end) { // Allocation successful. We only update state now to deal with potential errors self.head = end; self.used_bytes += extra_used; Some((base, slot)) } else { None } } pub fn free(&mut self, id: u16) { self.slots[id as usize].set_free(true); if self.alloc_list_tail == Some(id) { // This is the oldest slot. We now have to free it and any other connected free slots in the chain. let mut max_end = self.tail; let mut current = id as usize; while self.slots[current].is_free() { let slot = &mut self.slots[current]; max_end = slot.get_end_offset(); let next_slot = slot.get_next_slot(); self.alloc_list_tail = next_slot; // Insert the slot into the free list slot.set_next_slot(self.free_list); self.free_list = Some(current as u16); if let Some(next_slot) = next_slot { current = next_slot as usize; } else { // The alloc list is now empty self.alloc_list_head = None; break; } } if max_end == self.tail { self.used_bytes = 0; } else if max_end < self.tail { let released = (self.size - self.tail) + max_end; self.used_bytes -= released; } else { self.used_bytes -= max_end - self.tail; } self.tail = max_end; } } fn push_slot(&mut self, end_offset: vk::DeviceSize) -> Option { let next_slot= self.free_list.or_else(|| { self.expand_slots(16); self.free_list })?; if let Some(head) = self.alloc_list_head { self.slots[head as usize].set_next_slot(Some(next_slot)); } else { // If we have no head we also have no tail self.alloc_list_tail = Some(next_slot); } let slot = &mut self.slots[next_slot as usize]; self.free_list = slot.get_next_slot(); slot.set_free(false); slot.set_end_offset(end_offset); slot.set_next_slot(None); self.alloc_list_head = Some(next_slot); Some(next_slot) } fn expand_slots(&mut self, mut new: u16) { if (self.slots.len() + new as usize) > RingAllocatorSlot::MAX_SLOT_INDEX { new = (RingAllocatorSlot::MAX_SLOT_INDEX - self.slots.len()) as u16; } if new == 0 { return; } self.slots.reserve(new as usize); let base = self.slots.len() as u16; for i in base..(base + new - 1) { self.slots.push(RingAllocatorSlot::new(Some(i + 1))) } self.slots.push(RingAllocatorSlot::new(None)); if let Some(head) = self.free_list { self.slots[head as usize].set_next_slot(Some(base)); } else { self.free_list = Some(base); } } } struct RingAllocatorSlot { /// Packed data format: /// - `end_offset` (bits 0-46): The offset of the first byte after the memory regions. /// - `free` (bit 47): Set to true if the allocation has been freed. /// - `next_slot` (bits 48-63): The index of the next slot in the linked list. If all bits are /// set to 1 this slot is the end of the list. payload: u64, } impl RingAllocatorSlot { const MAX_END_OFFSET: u64 = Self::END_OFFSET_MASK; const MAX_SLOT_INDEX: usize = ((u16::MAX - 1) as usize); const END_OFFSET_MASK: u64 = (u64::MAX >> 17); const FREE_MASK: u64 = (1u64 << 47); const NEXT_SLOT_OFFSET: u8 = 48; const NEXT_SLOT_MASK: u64 = ((u16::MAX as u64) << 48); #[inline] fn new(next_slot: Option) -> Self { let mut result = Self { payload: Self::NEXT_SLOT_MASK, }; result.set_next_slot(next_slot); result } #[inline] fn set_end_offset(&mut self, end_offset: vk::DeviceSize) { assert_eq!(end_offset & !Self::END_OFFSET_MASK, 0u64); self.payload = (self.payload & !Self::END_OFFSET_MASK) | end_offset; } #[inline] fn get_end_offset(&self) -> vk::DeviceSize { self.payload & Self::END_OFFSET_MASK } #[inline] fn set_free(&mut self, free: bool) { let mut tmp = self.payload & !Self::FREE_MASK; if free { tmp |= Self::FREE_MASK; } self.payload = tmp; } #[inline] fn is_free(&self) -> bool { (self.payload & Self::FREE_MASK) == Self::FREE_MASK } #[inline] fn set_next_slot(&mut self, next_slot: Option) { let mut tmp = self.payload & !Self::NEXT_SLOT_MASK; if let Some(next_slot) = next_slot { let next_slot = next_slot as usize; assert!(next_slot <= Self::MAX_SLOT_INDEX); tmp |= (next_slot as u64) << Self::NEXT_SLOT_OFFSET; } else { tmp |= Self::NEXT_SLOT_MASK; } self.payload = tmp; } #[inline] fn get_next_slot(&self) -> Option { let masked = self.payload & Self::NEXT_SLOT_MASK; if masked == Self::NEXT_SLOT_MASK { None } else { Some((masked >> Self::NEXT_SLOT_OFFSET) as u16) } } } // Make sure we didnt mess up the bitmasks const_assert_eq!(RingAllocatorSlot::END_OFFSET_MASK & RingAllocatorSlot::FREE_MASK & RingAllocatorSlot::NEXT_SLOT_MASK, 0u64); const_assert_eq!(RingAllocatorSlot::END_OFFSET_MASK | RingAllocatorSlot::FREE_MASK | RingAllocatorSlot::NEXT_SLOT_MASK, u64::MAX); const_assert_eq!(RingAllocatorSlot::NEXT_SLOT_MASK >> RingAllocatorSlot::NEXT_SLOT_OFFSET, u16::MAX as u64); const_assert_eq!(((RingAllocatorSlot::MAX_SLOT_INDEX as u64) << RingAllocatorSlot::NEXT_SLOT_OFFSET) & !RingAllocatorSlot::NEXT_SLOT_MASK, 0u64); #[cfg(test)] mod tests { use rand::prelude::SliceRandom; use super::*; #[test] fn test_ring_allocator_slot() { let mut slot = RingAllocatorSlot::new(None); assert_eq!(slot.get_next_slot(), None); slot.set_free(true); assert_eq!(slot.is_free(), true); assert_eq!(slot.get_next_slot(), None); slot.set_end_offset(0); assert_eq!(slot.get_end_offset(), 0); assert_eq!(slot.is_free(), true); assert_eq!(slot.get_next_slot(), None); slot.set_end_offset(2355); assert_eq!(slot.get_end_offset(), 2355); assert_eq!(slot.is_free(), true); assert_eq!(slot.get_next_slot(), None); slot.set_end_offset(RingAllocatorSlot::MAX_END_OFFSET); assert_eq!(slot.get_end_offset(), RingAllocatorSlot::MAX_END_OFFSET); assert_eq!(slot.is_free(), true); assert_eq!(slot.get_next_slot(), None); slot.set_free(false); assert_eq!(slot.is_free(), false); assert_eq!(slot.get_next_slot(), None); slot.set_end_offset(0); assert_eq!(slot.get_end_offset(), 0); assert_eq!(slot.is_free(), false); assert_eq!(slot.get_next_slot(), None); slot.set_end_offset(2355); assert_eq!(slot.get_end_offset(), 2355); assert_eq!(slot.is_free(), false); assert_eq!(slot.get_next_slot(), None); slot.set_end_offset(RingAllocatorSlot::MAX_END_OFFSET); assert_eq!(slot.get_end_offset(), RingAllocatorSlot::MAX_END_OFFSET); assert_eq!(slot.is_free(), false); assert_eq!(slot.get_next_slot(), None); let mut slot = RingAllocatorSlot::new(Some(0)); assert_eq!(slot.get_next_slot(), Some(0)); slot.set_free(true); assert_eq!(slot.is_free(), true); assert_eq!(slot.get_next_slot(), Some(0)); slot.set_end_offset(0); assert_eq!(slot.get_end_offset(), 0); assert_eq!(slot.is_free(), true); assert_eq!(slot.get_next_slot(), Some(0)); slot.set_end_offset(2355); assert_eq!(slot.get_end_offset(), 2355); assert_eq!(slot.is_free(), true); assert_eq!(slot.get_next_slot(), Some(0)); slot.set_end_offset(RingAllocatorSlot::MAX_END_OFFSET); assert_eq!(slot.get_end_offset(), RingAllocatorSlot::MAX_END_OFFSET); assert_eq!(slot.is_free(), true); assert_eq!(slot.get_next_slot(), Some(0)); slot.set_free(false); assert_eq!(slot.is_free(), false); assert_eq!(slot.get_next_slot(), Some(0)); slot.set_end_offset(0); assert_eq!(slot.get_end_offset(), 0); assert_eq!(slot.is_free(), false); assert_eq!(slot.get_next_slot(), Some(0)); slot.set_end_offset(2355); assert_eq!(slot.get_end_offset(), 2355); assert_eq!(slot.is_free(), false); assert_eq!(slot.get_next_slot(), Some(0)); slot.set_end_offset(RingAllocatorSlot::MAX_END_OFFSET); assert_eq!(slot.get_end_offset(), RingAllocatorSlot::MAX_END_OFFSET); assert_eq!(slot.is_free(), false); assert_eq!(slot.get_next_slot(), Some(0)); let mut slot = RingAllocatorSlot::new(Some(4652)); assert_eq!(slot.get_next_slot(), Some(4652)); slot.set_free(true); assert_eq!(slot.is_free(), true); assert_eq!(slot.get_next_slot(), Some(4652)); slot.set_end_offset(0); assert_eq!(slot.get_end_offset(), 0); assert_eq!(slot.is_free(), true); assert_eq!(slot.get_next_slot(), Some(4652)); slot.set_end_offset(2355); assert_eq!(slot.get_end_offset(), 2355); assert_eq!(slot.is_free(), true); assert_eq!(slot.get_next_slot(), Some(4652)); slot.set_end_offset(RingAllocatorSlot::MAX_END_OFFSET); assert_eq!(slot.get_end_offset(), RingAllocatorSlot::MAX_END_OFFSET); assert_eq!(slot.is_free(), true); assert_eq!(slot.get_next_slot(), Some(4652)); slot.set_free(false); assert_eq!(slot.is_free(), false); assert_eq!(slot.get_next_slot(), Some(4652)); slot.set_end_offset(0); assert_eq!(slot.get_end_offset(), 0); assert_eq!(slot.is_free(), false); assert_eq!(slot.get_next_slot(), Some(4652)); slot.set_end_offset(2355); assert_eq!(slot.get_end_offset(), 2355); assert_eq!(slot.is_free(), false); assert_eq!(slot.get_next_slot(), Some(4652)); slot.set_end_offset(RingAllocatorSlot::MAX_END_OFFSET); assert_eq!(slot.get_end_offset(), RingAllocatorSlot::MAX_END_OFFSET); assert_eq!(slot.is_free(), false); assert_eq!(slot.get_next_slot(), Some(4652)); let mut slot = RingAllocatorSlot::new(Some(RingAllocatorSlot::MAX_SLOT_INDEX as u16)); assert_eq!(slot.get_next_slot(), Some(RingAllocatorSlot::MAX_SLOT_INDEX as u16)); slot.set_free(true); assert_eq!(slot.is_free(), true); assert_eq!(slot.get_next_slot(), Some(RingAllocatorSlot::MAX_SLOT_INDEX as u16)); slot.set_end_offset(0); assert_eq!(slot.get_end_offset(), 0); assert_eq!(slot.is_free(), true); assert_eq!(slot.get_next_slot(), Some(RingAllocatorSlot::MAX_SLOT_INDEX as u16)); slot.set_end_offset(2355); assert_eq!(slot.get_end_offset(), 2355); assert_eq!(slot.is_free(), true); assert_eq!(slot.get_next_slot(), Some(RingAllocatorSlot::MAX_SLOT_INDEX as u16)); slot.set_end_offset(RingAllocatorSlot::MAX_END_OFFSET); assert_eq!(slot.get_end_offset(), RingAllocatorSlot::MAX_END_OFFSET); assert_eq!(slot.is_free(), true); assert_eq!(slot.get_next_slot(), Some(RingAllocatorSlot::MAX_SLOT_INDEX as u16)); slot.set_free(false); assert_eq!(slot.is_free(), false); assert_eq!(slot.get_next_slot(), Some(RingAllocatorSlot::MAX_SLOT_INDEX as u16)); slot.set_end_offset(0); assert_eq!(slot.get_end_offset(), 0); assert_eq!(slot.is_free(), false); assert_eq!(slot.get_next_slot(), Some(RingAllocatorSlot::MAX_SLOT_INDEX as u16)); slot.set_end_offset(2355); assert_eq!(slot.get_end_offset(), 2355); assert_eq!(slot.is_free(), false); assert_eq!(slot.get_next_slot(), Some(RingAllocatorSlot::MAX_SLOT_INDEX as u16)); slot.set_end_offset(RingAllocatorSlot::MAX_END_OFFSET); assert_eq!(slot.get_end_offset(), RingAllocatorSlot::MAX_END_OFFSET); assert_eq!(slot.is_free(), false); assert_eq!(slot.get_next_slot(), Some(RingAllocatorSlot::MAX_SLOT_INDEX as u16)); } #[test] fn test_alloc_free() { let mut allocator = RingAllocator::new(1024); assert_eq!(allocator.used_byte_count(), 0); assert_eq!(allocator.free_byte_count(), 1024); assert_eq!(allocator.is_empty(), true); let alloc = allocator.allocate(128, 1).unwrap(); assert_eq!(alloc.0, 0); assert_eq!(allocator.used_byte_count(), 128); assert_eq!(allocator.free_byte_count(), 1024 - 128); assert_eq!(allocator.is_empty(), false); allocator.free(alloc.1); assert_eq!(allocator.used_byte_count(), 0); assert_eq!(allocator.free_byte_count(), 1024); assert_eq!(allocator.is_empty(), true); let mut allocs = Vec::with_capacity(16); for _ in 0..1024 { for i in 0..16u64 { let (_, alloc) = allocator.allocate(16, 1).unwrap(); allocs.push(alloc); let used_size = (i + 1) * 16; assert_eq!(allocator.used_byte_count(), used_size); assert_eq!(allocator.free_byte_count(), 1024 - used_size); assert_eq!(allocator.is_empty(), false); } allocs.as_mut_slice().shuffle(&mut rand::thread_rng()); for alloc in allocs.iter() { assert_eq!(allocator.is_empty(), false); allocator.free(*alloc); } allocs.clear(); assert_eq!(allocator.used_byte_count(), 0); assert_eq!(allocator.free_byte_count(), 1024); assert_eq!(allocator.is_empty(), true); } } #[test] fn test_alloc_fail() { let mut allocator = RingAllocator::new(1024); assert_eq!(allocator.used_byte_count(), 0); assert_eq!(allocator.free_byte_count(), 1024); assert_eq!(allocator.is_empty(), true); let mut allocs0 = Vec::with_capacity(4); let mut allocs1 = Vec::with_capacity(12); for _ in 0..4 { let (_, alloc) = allocator.allocate(64, 1).unwrap(); allocs0.push(alloc); } for _ in 0..12 { let (_, alloc) = allocator.allocate(64, 1).unwrap(); allocs1.push(alloc); } assert_eq!(allocator.used_byte_count(), 1024); assert_eq!(allocator.free_byte_count(), 0); assert_eq!(allocator.is_empty(), false); assert_eq!(allocator.allocate(1, 1), None); assert_eq!(allocator.allocate(16, 1), None); assert_eq!(allocator.allocate(1024, 1), None); assert_eq!(allocator.allocate(2348793, 1), None); for alloc in allocs0 { allocator.free(alloc); } let mut allocs0 = Vec::with_capacity(4); for _ in 0..4 { let (_, alloc) = allocator.allocate(64, 1).unwrap(); allocs0.push(alloc); } assert_eq!(allocator.used_byte_count(), 1024); assert_eq!(allocator.free_byte_count(), 0); assert_eq!(allocator.is_empty(), false); assert_eq!(allocator.allocate(1, 1), None); assert_eq!(allocator.allocate(16, 1), None); assert_eq!(allocator.allocate(1024, 1), None); assert_eq!(allocator.allocate(2348793, 1), None); } } ================================================ FILE: core/natives/src/util/format.rs ================================================ use std::cmp::Ordering; use std::fmt::{Debug, Formatter}; use std::hash::{Hash, Hasher}; use ash::vk; #[derive(Eq, Copy, Clone, Debug)] pub struct CompatibilityClass { name: &'static str, } macro_rules! define_compatibility_class { ($name: ident) => { pub const $name: CompatibilityClass = CompatibilityClass::new(stringify!($name)); } } impl CompatibilityClass { pub const fn new(name: &'static str) -> Self { CompatibilityClass { name } } pub const fn get_name(&self) -> &'static str { self.name } define_compatibility_class!(BIT8); define_compatibility_class!(BIT16); define_compatibility_class!(BIT24); define_compatibility_class!(BIT32); define_compatibility_class!(BIT32_G8B8G8R8); define_compatibility_class!(BIT32_B8G8R8G8); define_compatibility_class!(BIT48); define_compatibility_class!(BIT64); define_compatibility_class!(BIT64_R10G10B10A10); define_compatibility_class!(BIT64_G10B10G10R10); define_compatibility_class!(BIT64_B10G10R10G10); define_compatibility_class!(BIT64_R12G12B12A12); define_compatibility_class!(BIT64_G12B12G12R12); define_compatibility_class!(BIT64_B12G12R12G12); define_compatibility_class!(BIT64_G16B16G16R16); define_compatibility_class!(BIT64_B16G16R16G16); define_compatibility_class!(BIT96); define_compatibility_class!(BIT128); define_compatibility_class!(BIT192); define_compatibility_class!(BIT256); define_compatibility_class!(BC1_RGB); define_compatibility_class!(BC1_RGBA); define_compatibility_class!(BC2); define_compatibility_class!(BC3); define_compatibility_class!(BC4); define_compatibility_class!(BC5); define_compatibility_class!(BC6H); define_compatibility_class!(BC7); define_compatibility_class!(ETC2_RGB); define_compatibility_class!(ETC2_RGBA); define_compatibility_class!(ETC2_EAC_RGBA); define_compatibility_class!(EAC_R); define_compatibility_class!(EAC_RG); define_compatibility_class!(ASTC_4X4); define_compatibility_class!(ASTC_5X4); define_compatibility_class!(ASTC_5X5); define_compatibility_class!(ASTC_6X5); define_compatibility_class!(ASTC_6X6); define_compatibility_class!(ASTC_8X5); define_compatibility_class!(ASTC_8X6); define_compatibility_class!(ASTC_8X8); define_compatibility_class!(ASTC_10X5); define_compatibility_class!(ASTC_10X6); define_compatibility_class!(ASTC_10X8); define_compatibility_class!(ASTC_10X10); define_compatibility_class!(ASTC_12X10); define_compatibility_class!(ASTC_12X12); define_compatibility_class!(D16); define_compatibility_class!(D24); define_compatibility_class!(D32); define_compatibility_class!(S8); define_compatibility_class!(D16S8); define_compatibility_class!(D24S8); define_compatibility_class!(D32S8); define_compatibility_class!(PLANE3_8BIT_420); define_compatibility_class!(PLANE2_8BIT_420); define_compatibility_class!(PLANE3_8BIT_422); define_compatibility_class!(PLANE2_8BIT_422); define_compatibility_class!(PLANE3_8BIT_444); define_compatibility_class!(PLANE3_10BIT_420); define_compatibility_class!(PLANE2_10BIT_420); define_compatibility_class!(PLANE3_10BIT_422); define_compatibility_class!(PLANE2_10BIT_422); define_compatibility_class!(PLANE3_10BIT_444); define_compatibility_class!(PLANE3_12BIT_420); define_compatibility_class!(PLANE2_12BIT_420); define_compatibility_class!(PLANE3_12BIT_422); define_compatibility_class!(PLANE2_12BIT_422); define_compatibility_class!(PLANE3_12BIT_444); define_compatibility_class!(PLANE3_16BIT_420); define_compatibility_class!(PLANE2_16BIT_420); define_compatibility_class!(PLANE3_16BIT_422); define_compatibility_class!(PLANE2_16BIT_422); define_compatibility_class!(PLANE3_16BIT_444); } impl PartialEq for CompatibilityClass { fn eq(&self, other: &Self) -> bool { std::ptr::eq(self.name, other.name) } } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub enum ClearColorType { Float, Int32, Uint32, } impl ClearColorType { pub const fn make_zero_clear(&self) -> vk::ClearColorValue { match self { Self::Float => { vk::ClearColorValue { float32: [0f32; 4] } } Self::Int32 => { vk::ClearColorValue { int32: [0i32; 4] } } Self::Uint32 => { vk::ClearColorValue { uint32: [0u32; 4] } } } } } #[derive(Copy, Clone, Eq)] pub struct Format { format: vk::Format, compatibility_class: CompatibilityClass, clear_color_type: Option, } macro_rules! define_formats { ($($name:ident, $compatibility_class:expr, $channel_count:expr, $clear_color_type:expr);+) => { pub const fn format_for(format: vk::Format) -> &'static Format { match format { $( ash::vk::Format::$name => &Self::$name, )+ _ => { panic!("Unknown format!") } } } $(pub const $name : Format = Format::new(ash::vk::Format::$name, $compatibility_class, $channel_count, $clear_color_type);)+ } } impl Format { pub const fn new(format: vk::Format, compatibility_class: CompatibilityClass, _channel_count: u32, clear_color_type: Option) -> Self { Format { format, compatibility_class, clear_color_type } } pub const fn get_format(&self) -> vk::Format { self.format } pub const fn get_compatibility_class(&self) -> CompatibilityClass { self.compatibility_class } pub const fn get_clear_color_type(&self) -> Option { self.clear_color_type } pub fn is_compatible_with(&self, other: &Format) -> bool { self.compatibility_class == other.compatibility_class } define_formats!( R4G4_UNORM_PACK8, CompatibilityClass::BIT8, 2, Some(ClearColorType::Float); R4G4B4A4_UNORM_PACK16, CompatibilityClass::BIT16, 4, Some(ClearColorType::Float); B4G4R4A4_UNORM_PACK16, CompatibilityClass::BIT16, 4, Some(ClearColorType::Float); R5G6B5_UNORM_PACK16, CompatibilityClass::BIT16, 3, Some(ClearColorType::Float); B5G6R5_UNORM_PACK16, CompatibilityClass::BIT16, 3, Some(ClearColorType::Float); R5G5B5A1_UNORM_PACK16, CompatibilityClass::BIT16, 4, Some(ClearColorType::Float); B5G5R5A1_UNORM_PACK16, CompatibilityClass::BIT16, 4, Some(ClearColorType::Float); A1R5G5B5_UNORM_PACK16, CompatibilityClass::BIT16, 4, Some(ClearColorType::Float); R8_UNORM, CompatibilityClass::BIT8, 1, Some(ClearColorType::Float); R8_SNORM, CompatibilityClass::BIT8, 1, Some(ClearColorType::Float); R8_USCALED, CompatibilityClass::BIT8, 1, Some(ClearColorType::Float); R8_SSCALED, CompatibilityClass::BIT8, 1, Some(ClearColorType::Float); R8_UINT, CompatibilityClass::BIT8, 1, Some(ClearColorType::Uint32); R8_SINT, CompatibilityClass::BIT8, 1, Some(ClearColorType::Int32); R8_SRGB, CompatibilityClass::BIT8, 1, Some(ClearColorType::Float); R8G8_UNORM, CompatibilityClass::BIT16, 2, Some(ClearColorType::Float); R8G8_SNORM, CompatibilityClass::BIT16, 2, Some(ClearColorType::Float); R8G8_USCALED, CompatibilityClass::BIT16, 2, Some(ClearColorType::Float); R8G8_SSCALED, CompatibilityClass::BIT16, 2, Some(ClearColorType::Float); R8G8_UINT, CompatibilityClass::BIT16, 2, Some(ClearColorType::Uint32); R8G8_SINT, CompatibilityClass::BIT16, 2, Some(ClearColorType::Int32); R8G8_SRGB, CompatibilityClass::BIT16, 2, Some(ClearColorType::Float); R8G8B8_UNORM, CompatibilityClass::BIT24, 3, Some(ClearColorType::Float); R8G8B8_SNORM, CompatibilityClass::BIT24, 3, Some(ClearColorType::Float); R8G8B8_USCALED, CompatibilityClass::BIT24, 3, Some(ClearColorType::Float); R8G8B8_SSCALED, CompatibilityClass::BIT24, 3, Some(ClearColorType::Float); R8G8B8_UINT, CompatibilityClass::BIT24, 3, Some(ClearColorType::Uint32); R8G8B8_SINT, CompatibilityClass::BIT24, 3, Some(ClearColorType::Int32); R8G8B8_SRGB, CompatibilityClass::BIT24, 3, Some(ClearColorType::Float); B8G8R8_UNORM, CompatibilityClass::BIT24, 3, Some(ClearColorType::Float); B8G8R8_SNORM, CompatibilityClass::BIT24, 3, Some(ClearColorType::Float); B8G8R8_USCALED, CompatibilityClass::BIT24, 3, Some(ClearColorType::Float); B8G8R8_SSCALED, CompatibilityClass::BIT24, 3, Some(ClearColorType::Float); B8G8R8_UINT, CompatibilityClass::BIT24, 3, Some(ClearColorType::Uint32); B8G8R8_SINT, CompatibilityClass::BIT24, 3, Some(ClearColorType::Int32); B8G8R8_SRGB, CompatibilityClass::BIT24, 3, Some(ClearColorType::Float); R8G8B8A8_UNORM, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); R8G8B8A8_SNORM, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); R8G8B8A8_USCALED, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); R8G8B8A8_SSCALED, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); R8G8B8A8_UINT, CompatibilityClass::BIT32, 4, Some(ClearColorType::Uint32); R8G8B8A8_SINT, CompatibilityClass::BIT32, 4, Some(ClearColorType::Int32); R8G8B8A8_SRGB, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); B8G8R8A8_UNORM, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); B8G8R8A8_SNORM, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); B8G8R8A8_USCALED, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); B8G8R8A8_SSCALED, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); B8G8R8A8_UINT, CompatibilityClass::BIT32, 4, Some(ClearColorType::Uint32); B8G8R8A8_SINT, CompatibilityClass::BIT32, 4, Some(ClearColorType::Int32); B8G8R8A8_SRGB, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); A8B8G8R8_UNORM_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); A8B8G8R8_SNORM_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); A8B8G8R8_USCALED_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); A8B8G8R8_SSCALED_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); A8B8G8R8_UINT_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Uint32); A8B8G8R8_SINT_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Int32); A8B8G8R8_SRGB_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); A2R10G10B10_UNORM_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); A2R10G10B10_SNORM_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); A2R10G10B10_USCALED_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); A2R10G10B10_SSCALED_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); A2R10G10B10_UINT_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Uint32); A2R10G10B10_SINT_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Int32); A2B10G10R10_UNORM_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); A2B10G10R10_SNORM_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); A2B10G10R10_USCALED_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); A2B10G10R10_SSCALED_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Float); A2B10G10R10_UINT_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Uint32); A2B10G10R10_SINT_PACK32, CompatibilityClass::BIT32, 4, Some(ClearColorType::Int32); R16_UNORM, CompatibilityClass::BIT16, 1, Some(ClearColorType::Float); R16_SNORM, CompatibilityClass::BIT16, 1, Some(ClearColorType::Float); R16_USCALED, CompatibilityClass::BIT16, 1, Some(ClearColorType::Float); R16_SSCALED, CompatibilityClass::BIT16, 1, Some(ClearColorType::Float); R16_UINT, CompatibilityClass::BIT16, 1, Some(ClearColorType::Uint32); R16_SINT, CompatibilityClass::BIT16, 1, Some(ClearColorType::Int32); R16_SFLOAT, CompatibilityClass::BIT16, 1, Some(ClearColorType::Float); R16G16_UNORM, CompatibilityClass::BIT32, 2, Some(ClearColorType::Float); R16G16_SNORM, CompatibilityClass::BIT32, 2, Some(ClearColorType::Float); R16G16_USCALED, CompatibilityClass::BIT32, 2, Some(ClearColorType::Float); R16G16_SSCALED, CompatibilityClass::BIT32, 2, Some(ClearColorType::Float); R16G16_UINT, CompatibilityClass::BIT32, 2, Some(ClearColorType::Uint32); R16G16_SINT, CompatibilityClass::BIT32, 2, Some(ClearColorType::Int32); R16G16_SFLOAT, CompatibilityClass::BIT32, 2, Some(ClearColorType::Float); R16G16B16_UNORM, CompatibilityClass::BIT48, 3, Some(ClearColorType::Float); R16G16B16_SNORM, CompatibilityClass::BIT48, 3, Some(ClearColorType::Float); R16G16B16_USCALED, CompatibilityClass::BIT48, 3, Some(ClearColorType::Float); R16G16B16_SSCALED, CompatibilityClass::BIT48, 3, Some(ClearColorType::Float); R16G16B16_UINT, CompatibilityClass::BIT48, 3, Some(ClearColorType::Uint32); R16G16B16_SINT, CompatibilityClass::BIT48, 3, Some(ClearColorType::Int32); R16G16B16_SFLOAT, CompatibilityClass::BIT48, 3, Some(ClearColorType::Float); R16G16B16A16_UNORM, CompatibilityClass::BIT64, 4, Some(ClearColorType::Float); R16G16B16A16_SNORM, CompatibilityClass::BIT64, 4, Some(ClearColorType::Float); R16G16B16A16_USCALED, CompatibilityClass::BIT64, 4, Some(ClearColorType::Float); R16G16B16A16_SSCALED, CompatibilityClass::BIT64, 4, Some(ClearColorType::Float); R16G16B16A16_UINT, CompatibilityClass::BIT64, 4, Some(ClearColorType::Uint32); R16G16B16A16_SINT, CompatibilityClass::BIT64, 4, Some(ClearColorType::Int32); R16G16B16A16_SFLOAT, CompatibilityClass::BIT64, 4, Some(ClearColorType::Float); R32_UINT, CompatibilityClass::BIT32, 1, Some(ClearColorType::Uint32); R32_SINT, CompatibilityClass::BIT32, 1, Some(ClearColorType::Int32); R32_SFLOAT, CompatibilityClass::BIT32, 1, Some(ClearColorType::Float); R32G32_UINT, CompatibilityClass::BIT64, 2, Some(ClearColorType::Uint32); R32G32_SINT, CompatibilityClass::BIT64, 2, Some(ClearColorType::Int32); R32G32_SFLOAT, CompatibilityClass::BIT64, 2, Some(ClearColorType::Float); R32G32B32_UINT, CompatibilityClass::BIT96, 3, Some(ClearColorType::Uint32); R32G32B32_SINT, CompatibilityClass::BIT96, 3, Some(ClearColorType::Int32); R32G32B32_SFLOAT, CompatibilityClass::BIT96, 3, Some(ClearColorType::Float); R32G32B32A32_UINT, CompatibilityClass::BIT128, 4, Some(ClearColorType::Uint32); R32G32B32A32_SINT, CompatibilityClass::BIT128, 4, Some(ClearColorType::Int32); R32G32B32A32_SFLOAT, CompatibilityClass::BIT128, 4, Some(ClearColorType::Float); R64_UINT, CompatibilityClass::BIT64, 1, Some(ClearColorType::Uint32); R64_SINT, CompatibilityClass::BIT64, 1, Some(ClearColorType::Int32); R64_SFLOAT, CompatibilityClass::BIT64, 1, Some(ClearColorType::Float); R64G64_UINT, CompatibilityClass::BIT128, 2, Some(ClearColorType::Uint32); R64G64_SINT, CompatibilityClass::BIT128, 2, Some(ClearColorType::Int32); R64G64_SFLOAT, CompatibilityClass::BIT128, 2, Some(ClearColorType::Float); R64G64B64_UINT, CompatibilityClass::BIT192, 3, Some(ClearColorType::Uint32); R64G64B64_SINT, CompatibilityClass::BIT192, 3, Some(ClearColorType::Int32); R64G64B64_SFLOAT, CompatibilityClass::BIT192, 3, Some(ClearColorType::Float); R64G64B64A64_UINT, CompatibilityClass::BIT256, 4, Some(ClearColorType::Uint32); R64G64B64A64_SINT, CompatibilityClass::BIT256, 4, Some(ClearColorType::Int32); R64G64B64A64_SFLOAT, CompatibilityClass::BIT256, 4, Some(ClearColorType::Float); B10G11R11_UFLOAT_PACK32, CompatibilityClass::BIT32, 3, Some(ClearColorType::Float); E5B9G9R9_UFLOAT_PACK32, CompatibilityClass::BIT32, 3, Some(ClearColorType::Float); D16_UNORM, CompatibilityClass::D16, 1, None; X8_D24_UNORM_PACK32, CompatibilityClass::D24, 1, None; D32_SFLOAT, CompatibilityClass::D32, 1, None; S8_UINT, CompatibilityClass::S8, 1, None; D16_UNORM_S8_UINT, CompatibilityClass::D16S8, 2, None; D24_UNORM_S8_UINT, CompatibilityClass::D24S8, 2, None; D32_SFLOAT_S8_UINT, CompatibilityClass::D32S8, 2, None; BC1_RGB_UNORM_BLOCK, CompatibilityClass::BC1_RGB, 3, Some(ClearColorType::Float); BC1_RGB_SRGB_BLOCK, CompatibilityClass::BC1_RGB, 3, Some(ClearColorType::Float); BC1_RGBA_UNORM_BLOCK, CompatibilityClass::BC1_RGBA, 4, Some(ClearColorType::Float); BC1_RGBA_SRGB_BLOCK, CompatibilityClass::BC1_RGBA, 4, Some(ClearColorType::Float); BC2_UNORM_BLOCK, CompatibilityClass::BC2, 4, Some(ClearColorType::Float); BC2_SRGB_BLOCK, CompatibilityClass::BC2, 4, Some(ClearColorType::Float); BC3_UNORM_BLOCK, CompatibilityClass::BC3, 4, Some(ClearColorType::Float); BC3_SRGB_BLOCK, CompatibilityClass::BC3, 4, Some(ClearColorType::Float); BC4_UNORM_BLOCK, CompatibilityClass::BC4, 1, Some(ClearColorType::Float); BC4_SNORM_BLOCK, CompatibilityClass::BC4, 1, Some(ClearColorType::Float); BC5_UNORM_BLOCK, CompatibilityClass::BC5, 2, Some(ClearColorType::Float); BC5_SNORM_BLOCK, CompatibilityClass::BC5, 2, Some(ClearColorType::Float); BC6H_UFLOAT_BLOCK, CompatibilityClass::BC6H, 3, Some(ClearColorType::Float); BC6H_SFLOAT_BLOCK, CompatibilityClass::BC6H, 3, Some(ClearColorType::Float); BC7_UNORM_BLOCK, CompatibilityClass::BC7, 4, Some(ClearColorType::Float); BC7_SRGB_BLOCK, CompatibilityClass::BC7, 4, Some(ClearColorType::Float); ETC2_R8G8B8_UNORM_BLOCK, CompatibilityClass::ETC2_RGB, 3, Some(ClearColorType::Float); ETC2_R8G8B8_SRGB_BLOCK, CompatibilityClass::ETC2_RGB, 3, Some(ClearColorType::Float); ETC2_R8G8B8A1_UNORM_BLOCK, CompatibilityClass::ETC2_RGBA, 4, Some(ClearColorType::Float); ETC2_R8G8B8A1_SRGB_BLOCK, CompatibilityClass::ETC2_RGBA, 4, Some(ClearColorType::Float); ETC2_R8G8B8A8_UNORM_BLOCK, CompatibilityClass::ETC2_EAC_RGBA, 4, Some(ClearColorType::Float); ETC2_R8G8B8A8_SRGB_BLOCK, CompatibilityClass::ETC2_EAC_RGBA, 4, Some(ClearColorType::Float); EAC_R11_UNORM_BLOCK, CompatibilityClass::EAC_R, 1, Some(ClearColorType::Float); EAC_R11_SNORM_BLOCK, CompatibilityClass::EAC_R, 1, Some(ClearColorType::Float); EAC_R11G11_UNORM_BLOCK, CompatibilityClass::EAC_RG, 2, Some(ClearColorType::Float); EAC_R11G11_SNORM_BLOCK, CompatibilityClass::EAC_RG, 2, Some(ClearColorType::Float); ASTC_4X4_UNORM_BLOCK, CompatibilityClass::ASTC_4X4, 4, Some(ClearColorType::Float); ASTC_4X4_SRGB_BLOCK, CompatibilityClass::ASTC_4X4, 4, Some(ClearColorType::Float); ASTC_5X4_UNORM_BLOCK, CompatibilityClass::ASTC_5X4, 4, Some(ClearColorType::Float); ASTC_5X4_SRGB_BLOCK, CompatibilityClass::ASTC_5X4, 4, Some(ClearColorType::Float); ASTC_5X5_UNORM_BLOCK, CompatibilityClass::ASTC_5X5, 4, Some(ClearColorType::Float); ASTC_5X5_SRGB_BLOCK, CompatibilityClass::ASTC_5X5, 4, Some(ClearColorType::Float); ASTC_6X5_UNORM_BLOCK, CompatibilityClass::ASTC_6X5, 4, Some(ClearColorType::Float); ASTC_6X5_SRGB_BLOCK, CompatibilityClass::ASTC_6X5, 4, Some(ClearColorType::Float); ASTC_6X6_UNORM_BLOCK, CompatibilityClass::ASTC_6X6, 4, Some(ClearColorType::Float); ASTC_6X6_SRGB_BLOCK, CompatibilityClass::ASTC_6X6, 4, Some(ClearColorType::Float); ASTC_8X5_UNORM_BLOCK, CompatibilityClass::ASTC_8X5, 4, Some(ClearColorType::Float); ASTC_8X5_SRGB_BLOCK, CompatibilityClass::ASTC_8X5, 4, Some(ClearColorType::Float); ASTC_8X6_UNORM_BLOCK, CompatibilityClass::ASTC_8X6, 4, Some(ClearColorType::Float); ASTC_8X6_SRGB_BLOCK, CompatibilityClass::ASTC_8X6, 4, Some(ClearColorType::Float); ASTC_8X8_UNORM_BLOCK, CompatibilityClass::ASTC_8X8, 4, Some(ClearColorType::Float); ASTC_8X8_SRGB_BLOCK, CompatibilityClass::ASTC_8X8, 4, Some(ClearColorType::Float); ASTC_10X5_UNORM_BLOCK, CompatibilityClass::ASTC_10X5, 4, Some(ClearColorType::Float); ASTC_10X5_SRGB_BLOCK, CompatibilityClass::ASTC_10X5, 4, Some(ClearColorType::Float); ASTC_10X6_UNORM_BLOCK, CompatibilityClass::ASTC_10X6, 4, Some(ClearColorType::Float); ASTC_10X6_SRGB_BLOCK, CompatibilityClass::ASTC_10X6, 4, Some(ClearColorType::Float); ASTC_10X8_UNORM_BLOCK, CompatibilityClass::ASTC_10X8, 4, Some(ClearColorType::Float); ASTC_10X8_SRGB_BLOCK, CompatibilityClass::ASTC_10X8, 4, Some(ClearColorType::Float); ASTC_10X10_UNORM_BLOCK, CompatibilityClass::ASTC_10X10, 4, Some(ClearColorType::Float); ASTC_10X10_SRGB_BLOCK, CompatibilityClass::ASTC_10X10, 4, Some(ClearColorType::Float); ASTC_12X10_UNORM_BLOCK, CompatibilityClass::ASTC_12X10, 4, Some(ClearColorType::Float); ASTC_12X10_SRGB_BLOCK, CompatibilityClass::ASTC_12X10, 4, Some(ClearColorType::Float); ASTC_12X12_UNORM_BLOCK, CompatibilityClass::ASTC_12X12, 4, Some(ClearColorType::Float); ASTC_12X12_SRGB_BLOCK, CompatibilityClass::ASTC_12X12, 4, Some(ClearColorType::Float); G8B8G8R8_422_UNORM, CompatibilityClass::BIT32_G8B8G8R8, 4, Some(ClearColorType::Float); B8G8R8G8_422_UNORM, CompatibilityClass::BIT32_B8G8R8G8, 4, Some(ClearColorType::Float); G8_B8_R8_3PLANE_420_UNORM, CompatibilityClass::PLANE3_8BIT_420, 3, Some(ClearColorType::Float); G8_B8R8_2PLANE_420_UNORM, CompatibilityClass::PLANE2_8BIT_420, 3, Some(ClearColorType::Float); G8_B8_R8_3PLANE_422_UNORM, CompatibilityClass::PLANE3_8BIT_422, 3, Some(ClearColorType::Float); G8_B8R8_2PLANE_422_UNORM, CompatibilityClass::PLANE2_8BIT_422, 3, Some(ClearColorType::Float); G8_B8_R8_3PLANE_444_UNORM, CompatibilityClass::PLANE3_8BIT_444, 3, Some(ClearColorType::Float); R10X6_UNORM_PACK16, CompatibilityClass::BIT16, 1, Some(ClearColorType::Float); R10X6G10X6_UNORM_2PACK16, CompatibilityClass::BIT32, 2, Some(ClearColorType::Float); R10X6G10X6B10X6A10X6_UNORM_4PACK16, CompatibilityClass::BIT64_R10G10B10A10, 4, Some(ClearColorType::Float); G10X6B10X6G10X6R10X6_422_UNORM_4PACK16, CompatibilityClass::BIT64_G10B10G10R10, 4, Some(ClearColorType::Float); B10X6G10X6R10X6G10X6_422_UNORM_4PACK16, CompatibilityClass::BIT64_B10G10R10G10, 4, Some(ClearColorType::Float); G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, CompatibilityClass::PLANE3_10BIT_420, 3, Some(ClearColorType::Float); G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16, CompatibilityClass::PLANE2_10BIT_420, 3, Some(ClearColorType::Float); G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16, CompatibilityClass::PLANE3_10BIT_422, 3, Some(ClearColorType::Float); G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16, CompatibilityClass::PLANE2_10BIT_422, 3, Some(ClearColorType::Float); G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16, CompatibilityClass::PLANE3_10BIT_444, 3, Some(ClearColorType::Float); R12X4_UNORM_PACK16, CompatibilityClass::BIT16, 1, Some(ClearColorType::Float); R12X4G12X4_UNORM_2PACK16, CompatibilityClass::BIT32, 2, Some(ClearColorType::Float); R12X4G12X4B12X4A12X4_UNORM_4PACK16, CompatibilityClass::BIT64_R12G12B12A12, 4, Some(ClearColorType::Float); G12X4B12X4G12X4R12X4_422_UNORM_4PACK16, CompatibilityClass::BIT64_G12B12G12R12, 4, Some(ClearColorType::Float); B12X4G12X4R12X4G12X4_422_UNORM_4PACK16, CompatibilityClass::BIT64_B12G12R12G12, 4, Some(ClearColorType::Float); G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16, CompatibilityClass::PLANE3_12BIT_420, 3, Some(ClearColorType::Float); G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16, CompatibilityClass::PLANE2_12BIT_420, 3, Some(ClearColorType::Float); G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16, CompatibilityClass::PLANE3_12BIT_422, 3, Some(ClearColorType::Float); G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16, CompatibilityClass::PLANE2_12BIT_422, 3, Some(ClearColorType::Float); G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16, CompatibilityClass::PLANE3_12BIT_444, 3, Some(ClearColorType::Float); G16B16G16R16_422_UNORM, CompatibilityClass::BIT64_G16B16G16R16, 3, Some(ClearColorType::Float); B16G16R16G16_422_UNORM, CompatibilityClass::BIT64_B16G16R16G16, 3, Some(ClearColorType::Float); G16_B16_R16_3PLANE_420_UNORM, CompatibilityClass::PLANE3_16BIT_420, 3, Some(ClearColorType::Float); G16_B16R16_2PLANE_420_UNORM, CompatibilityClass::PLANE2_16BIT_420, 3, Some(ClearColorType::Float); G16_B16_R16_3PLANE_422_UNORM, CompatibilityClass::PLANE3_16BIT_422, 3, Some(ClearColorType::Float); G16_B16R16_2PLANE_422_UNORM, CompatibilityClass::PLANE2_16BIT_422, 3, Some(ClearColorType::Float); G16_B16_R16_3PLANE_444_UNORM, CompatibilityClass::PLANE3_16BIT_444, 3, Some(ClearColorType::Float) ); } impl PartialEq for Format { fn eq(&self, other: &Self) -> bool { self.format == other.format } } impl PartialOrd for Format { fn partial_cmp(&self, other: &Self) -> Option { self.format.partial_cmp(&other.format) } } impl Ord for Format { fn cmp(&self, other: &Self) -> Ordering { self.format.cmp(&other.format) } } impl Hash for Format { fn hash(&self, state: &mut H) { self.format.hash(state) } } impl Debug for Format { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_tuple("Format").field(&self.format).finish() } } impl Into for &Format { fn into(self) -> vk::Format { self.format } } ================================================ FILE: core/natives/src/util/id.rs ================================================ /// Utilities for globally unique identifiers. use std::cell::RefCell; use std::cmp::Ordering; use std::fmt::{Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::num::NonZeroU64; use std::sync::{Arc, Mutex}; use lazy_static::lazy_static; use crate::util::rand::Xoshiro256PlusPlus; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct UUID(NonZeroU64); lazy_static! { static ref UUID_SEEDER : Mutex = Mutex::new(Xoshiro256PlusPlus::from_seed([1u64, 1u64, 1u64, 1u64])); } thread_local! { static THREAD_UUID_SEEDER : RefCell = { let mut seeder = UUID_SEEDER.lock().unwrap(); seeder.jump(); RefCell::new(*seeder) } } impl UUID { pub fn new() -> Self { let id = THREAD_UUID_SEEDER.with(|seeder| seeder.borrow_mut().find(|id| *id != 0u64)).unwrap(); Self(NonZeroU64::new(id).unwrap()) } pub const fn from_raw(id: u64) -> Self { if id == 0u64 { panic!("Zero id") } Self(unsafe { NonZeroU64::new_unchecked(id) }) } pub const fn get_raw(&self) -> u64 { self.0.get() } } impl Debug for UUID { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("UUID({:#016X})", self.get_raw())) } } #[derive(Clone, Debug)] enum NameType { Static(&'static str), String(Arc), } impl NameType { const fn new_static(str: &'static str) -> Self { Self::Static(str) } fn new_string(str: String) -> Self { Self::String(Arc::new(str)) } fn get(&self) -> &str { match self { NameType::Static(str) => *str, NameType::String(str) => str.as_ref(), } } } /// A UUID generated from a string. /// /// NamedUUIDs use a predefined global id with the local id being calculated as the hash of a /// string. The name is stored along side the UUID for easy debugging or printing. The name is /// stored by Arc enabling fast Copying of the struct. #[derive(Clone)] pub struct NamedUUID { name: NameType, id: UUID, } impl NamedUUID { const fn hash_str_const(name: &str) -> u64 { xxhash_rust::const_xxh3::xxh3_64(name.as_bytes()) } fn hash_str(name: &str) -> u64 { xxhash_rust::xxh3::xxh3_64(name.as_bytes()) } /// Creates a new uuid based on the hash of the string. Calling this function with the same /// string will always return the same id. pub const fn from_str(name: &'static str) -> NamedUUID { let hash = Self::hash_str_const(name); NamedUUID { name: NameType::new_static(name), id: UUID::from_raw(hash) } } /// Creates a new uuid based on the hash of the string. Calling this function with the same /// string will always return the same id. pub fn from_string(name: String) -> NamedUUID { let hash = Self::hash_str(name.as_str()); NamedUUID { name: NameType::new_string(name), id: UUID::from_raw(hash) } } /// Creates a new random uuid with a string attached. Calling this function with the same /// string will not return the same id. pub fn with_str(name: &'static str) -> NamedUUID { NamedUUID { name: NameType::new_static(name), id: UUID::new() } } /// Creates a new random uuid with a string attached. Calling this function with the same /// string will not return the same id. pub fn with_string(name: String) -> NamedUUID { NamedUUID { name: NameType::new_string(name), id: UUID::new() } } /// Generates the uuid for a string. Does not store the name to allow for parsing non static /// strings pub const fn uuid_for(name: &str) -> UUID { UUID::from_raw(Self::hash_str_const(name)) } /// Returns the attached string pub fn get_name(&self) -> &str { self.name.get() } /// Returns the uuid pub fn get_uuid(&self) -> UUID { self.id } /// Utility function to clone a uuid that has a const string attached to it. /// This function will panic if the uuid has a non const string attached. pub const fn clone_const(&self) -> Self { match self.name { NameType::String(_) => { panic!("Cloned non const name") } NameType::Static(str) => { Self{ name: NameType::Static(str), id: self.id } } } } } impl PartialEq for NamedUUID { fn eq(&self, other: &Self) -> bool { self.id.eq(&other.id) } } impl Eq for NamedUUID { } impl PartialEq for NamedUUID { fn eq(&self, other: &UUID) -> bool { self.get_uuid().eq(other) } } impl PartialOrd for NamedUUID { fn partial_cmp(&self, other: &Self) -> Option { self.id.partial_cmp(&other.id) } } impl Ord for NamedUUID { fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) } } impl PartialOrd for NamedUUID { fn partial_cmp(&self, other: &UUID) -> Option { self.get_uuid().partial_cmp(other) } } impl Hash for NamedUUID { fn hash(&self, state: &mut H) { // The hash should be identical to the one generated from the uuid self.get_uuid().hash(state) } } impl Into for NamedUUID { fn into(self) -> UUID { self.get_uuid() } } impl Debug for NamedUUID { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let name = match &self.name { NameType::Static(str) => *str, NameType::String(str) => str.as_str() }; f.write_fmt(format_args!("NamedUUID{{\"{}\", {:?}}}", name, &self.id)) } } /// Utility macro to define new id types using a [`UUID`] internally. #[macro_export] macro_rules! define_uuid_type { ($vis:vis, $name:ident) => { #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] $vis struct $name(UUID); impl $name { $vis fn new() -> Self { Self(UUID::new()) } $vis fn from_uuid(raw: UUID) -> Self { Self(raw) } $vis fn as_uuid(&self) -> UUID { self.0 } } impl From<$name> for UUID { fn from(id: $name) -> Self { id.as_uuid() } } } } pub use define_uuid_type; ================================================ FILE: core/natives/src/util/mod.rs ================================================ pub mod id; pub mod rand; pub mod slice_splitter; pub mod alloc; pub mod vk; pub mod format; ================================================ FILE: core/natives/src/util/rand.rs ================================================ /// Implements the Xoshiro256++ random number algorithm. /// See https://prng.di.unimi.it/xoshiro256plusplus.c #[derive(Copy, Clone)] pub struct Xoshiro256PlusPlus { s: [u64; 4], } impl Xoshiro256PlusPlus { const JUMP : [u64; 4] = [0x180ec6d33cfd0abau64, 0xd5a61266f0c9392cu64, 0xa9582618e03fc9aau64, 0x39abdc4529b1661cu64]; const LONG_JUMP : [u64; 4] = [ 0x76e15d3efefdcbbfu64, 0xc5004e441c522fb3u64, 0x77710069854ee241u64, 0x39109bb02acbe635u64 ]; /// Creates a new random number generator with specified seed pub const fn from_seed(seed: [u64; 4]) -> Self { Self{ s: seed } } const fn rotl(x: u64, k: i32) -> u64 { (x << k) | (x >> (64i32 - k)) } /// Generates a new random number pub fn gen(&mut self) -> u64 { let result = u64::overflowing_add(Self::rotl(u64::overflowing_add(self.s[0], self.s[3]).0, 23), self.s[0]).0; let t = self.s[1] << 17; self.s[2] ^= self.s[0]; self.s[3] ^= self.s[1]; self.s[1] ^= self.s[2]; self.s[0] ^= self.s[3]; self.s[2] ^= t; self.s[3] = Self::rotl(self.s[3], 45); result } /// Utility function to create the jump and long_jump functions fn update_with(&mut self, update: [u64; SIZE]) { let mut s0 = 0u64; let mut s1 = 0u64; let mut s2 = 0u64; let mut s3 = 0u64; for i in 0..update.len() { for b in 0..64u32 { if (update[i] & (1u64 << b)) != 0u64 { s0 ^= self.s[0]; s1 ^= self.s[1]; s2 ^= self.s[2]; s3 ^= self.s[3]; } self.gen(); } } self.s[0] = s0; self.s[1] = s1; self.s[2] = s2; self.s[3] = s3; } /// This function is equivalent to 2^128 calls to [`Self::gen`]. It can be used to generate /// 2^128 non-overlapping subsequences for parallel computations. pub fn jump(&mut self) { self.update_with(Self::JUMP) } /// This function is equivalent to 2^192 calls to [`Self::gen`]. It can be used to generate /// 2^64 starting points from each of which [`Self::jump`] will generate 2^64 non-overlapping /// subsequences for parallel distributed computations. pub fn long_jump(&mut self) { self.update_with(Self::LONG_JUMP) } } impl Iterator for Xoshiro256PlusPlus { type Item = u64; fn next(&mut self) -> Option { Some(self.gen()) } } ================================================ FILE: core/natives/src/util/slice_splitter.rs ================================================ /// Enables mutable access to one element of a slice while still providing immutable access /// to other elements. pub struct Splitter<'a, T> { index: usize, head: &'a [T], tail: &'a [T], } impl<'a, T> Splitter<'a, T> { /// Creates a new Splitter providing mutable access to the element at the specified index. pub fn new<'b: 'a>(slice: &'b mut [T], index: usize) -> (Self, &'a mut T) { let (head, tail) = slice.split_at_mut(index); let (elem, tail) = tail.split_first_mut().unwrap(); (Self { index, head, tail }, elem) } /// Returns a reference to any element that is not mutably accessed. pub fn get(&self, index: usize) -> Option<&T> { if index < self.index { self.head.get(index) } else if index > self.index { self.tail.get(index - self.index - 1) } else { None } } } ================================================ FILE: core/natives/src/util/vk.rs ================================================ use ash::vk; use crate::prelude::*; #[inline] pub fn make_full_viewport(size: Vec2u32) -> vk::Viewport { vk::Viewport { x: 0.0, y: 0.0, width: size[0] as f32, height: size[1] as f32, min_depth: 0.0, max_depth: 1.0 } } #[inline] pub fn make_full_rect(size: Vec2u32) -> vk::Rect2D { vk::Rect2D { offset: vk::Offset2D{ x: 0, y: 0 }, extent: vk::Extent2D{ width: size[0], height: size[1] } } } ================================================ FILE: core/natives/src/vk/mod.rs ================================================ //! This is a legacy module //! //! Some of the structs and functions are still used in places but the ultimate goal is to either //! remove or move all of those and delete this module. pub mod objects; #[cfg(any(test, feature = "__internal_doc_test"))] pub mod test; ================================================ FILE: core/natives/src/vk/objects/buffer.rs ================================================ use std::cmp::Ordering; use std::fmt::{Debug, Formatter}; use std::hash::{Hash, Hasher}; use ash::vk; use ash::vk::Handle; use crate::objects::id::{BufferId, ObjectId}; use crate::prelude::*; #[derive(Copy, Clone)] pub struct Buffer { id: BufferId, handle: vk::Buffer, } impl Buffer { pub fn new(handle: vk::Buffer) -> Self { Self { id: BufferId::new(), handle, } } pub fn from_raw(id: BufferId, handle: vk::Buffer) -> Self { Self { id, handle } } pub fn get_id(&self) -> BufferId { self.id } pub fn get_handle(&self) -> vk::Buffer { self.handle } } impl PartialEq for Buffer { fn eq(&self, other: &Self) -> bool { self.id.eq(&other.id) } } impl Eq for Buffer { } impl PartialOrd for Buffer { fn partial_cmp(&self, other: &Self) -> Option { self.id.partial_cmp(&other.id) } } impl Ord for Buffer { fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) } } impl Hash for Buffer { fn hash(&self, state: &mut H) { self.id.hash(state) } } impl Debug for Buffer { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("vkBuffer(UUID: {:#016X}, Handle: {:#016X})", self.id.get_raw(), self.handle.as_raw())) } } impl From for BufferId { fn from(buffer: Buffer) -> Self { buffer.get_id() } } impl From for UUID { fn from(buffer: Buffer) -> UUID { buffer.get_id().as_uuid() } } #[derive(Copy, Clone, Debug)] pub struct BufferSpec { pub size: u64, } impl BufferSpec { pub const fn new(size: u64) -> Self { BufferSpec { size } } pub const fn get_size(&self) -> u64 { self.size } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct BufferRange { pub offset: u64, pub length: u64, } ================================================ FILE: core/natives/src/vk/objects/image.rs ================================================ use std::cmp::Ordering; use std::fmt::{Debug, Formatter}; use std::hash::{Hash, Hasher}; use ash::vk; use ash::vk::Handle; use crate::objects::id::{ImageId, ObjectId}; use crate::vk::objects::Format; use crate::prelude::*; #[derive(Copy, Clone)] pub struct Image { id: ImageId, handle: vk::Image, } impl Image { pub fn new(handle: vk::Image) -> Self { Self { id: ImageId::new(), handle, } } pub fn get_id(&self) -> ImageId { self.id } pub fn get_handle(&self) -> vk::Image { self.handle } } impl PartialEq for Image { fn eq(&self, other: &Self) -> bool { self.id.eq(&other.id) } } impl Eq for Image { } impl PartialOrd for Image { fn partial_cmp(&self, other: &Self) -> Option { self.id.partial_cmp(&other.id) } } impl Ord for Image { fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) } } impl Hash for Image { fn hash(&self, state: &mut H) { self.id.hash(state) } } impl Debug for Image { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("vkImage({:#016X}, {:#016X})", self.id.get_raw(), self.handle.as_raw())) } } impl From for ImageId { fn from(image: Image) -> Self { image.get_id() } } impl From for UUID { fn from(image: Image) -> Self { image.get_id().as_uuid() } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ImageSize { Type1D { width: u32, mip_levels: u32, array_layers: u32 }, Type2D { width: u32, height: u32, mip_levels: u32, array_layers: u32 }, Type3D { width: u32, height: u32, depth: u32, mip_levels: u32 }, } impl ImageSize { pub const fn make_1d(width: u32) -> Self { ImageSize::Type1D { width, mip_levels: 1, array_layers: 1 } } pub const fn make_1d_mip(width: u32, mip_levels: u32) -> Self { ImageSize::Type1D { width, mip_levels, array_layers: 1 } } pub const fn make_1d_array(width: u32, array_layers: u32) -> Self { ImageSize::Type1D { width, mip_levels: 1, array_layers } } pub const fn make_1d_array_mip(width: u32, array_layers: u32, mip_levels: u32) -> Self { ImageSize::Type1D { width, mip_levels, array_layers } } pub const fn make_2d(width: u32, height: u32) -> Self { ImageSize::Type2D { width, height, mip_levels: 1, array_layers: 1 } } pub const fn make_2d_mip(width: u32, height: u32, mip_levels: u32) -> Self { ImageSize::Type2D { width, height, mip_levels, array_layers: 1 } } pub const fn make_2d_array(width: u32, height: u32, array_layers: u32) -> Self { ImageSize::Type2D { width, height, mip_levels: 1, array_layers } } pub const fn make_2d_array_mip(width: u32, height: u32, array_layers: u32, mip_levels: u32) -> Self { ImageSize::Type2D { width, height, mip_levels, array_layers } } pub const fn make_3d(width: u32, height: u32, depth: u32) -> Self { ImageSize::Type3D { width, height, depth, mip_levels: 1 } } pub const fn make_3d_mip(width: u32, height: u32, depth: u32, mip_levels: u32) -> Self { ImageSize::Type3D { width, height, depth, mip_levels } } pub const fn get_vulkan_type(&self) -> vk::ImageType { match self { ImageSize::Type1D { .. } => vk::ImageType::TYPE_1D, ImageSize::Type2D { .. } => vk::ImageType::TYPE_2D, ImageSize::Type3D { .. } => vk::ImageType::TYPE_3D, } } pub const fn get_width(&self) -> u32 { match self { ImageSize::Type1D { width, .. } => *width, ImageSize::Type2D { width, .. } => *width, ImageSize::Type3D { width, .. } => *width } } pub const fn get_height(&self) -> u32 { match self { ImageSize::Type1D { .. } => 1, ImageSize::Type2D { height, .. } => *height, ImageSize::Type3D { height, .. } => *height } } pub const fn get_depth(&self) -> u32 { match self { ImageSize::Type1D { .. } => 1, ImageSize::Type2D { .. } => 1, ImageSize::Type3D { depth, .. } => *depth } } pub const fn get_array_layers(&self) -> u32 { match self { ImageSize::Type1D { array_layers, .. } => *array_layers, ImageSize::Type2D { array_layers, .. } => *array_layers, ImageSize::Type3D { .. } => 1, } } pub const fn get_mip_levels(&self) -> u32 { match self { ImageSize::Type1D { mip_levels, .. } => *mip_levels, ImageSize::Type2D { mip_levels, .. } => *mip_levels, ImageSize::Type3D { mip_levels, .. } => *mip_levels, } } pub const fn as_extent_3d(&self) -> ash::vk::Extent3D { match self { ImageSize::Type1D { width, .. } => ash::vk::Extent3D { width: *width, height: 1, depth: 1 }, ImageSize::Type2D { width, height, .. } => ash::vk::Extent3D { width: *width, height: *height, depth: 1 }, ImageSize::Type3D { width, height, depth, .. } => ash::vk::Extent3D { width: *width, height: *height, depth: *depth } } } pub fn fill_extent_3d(&self, extent: &mut ash::vk::Extent3D) { *extent = self.as_extent_3d(); } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct ImageSpec { pub format: &'static Format, pub sample_count: ash::vk::SampleCountFlags, pub size: ImageSize, } impl ImageSpec { pub const fn new(size: ImageSize, format: &'static Format, sample_count: vk::SampleCountFlags) -> Self { ImageSpec { format, size, sample_count } } pub const fn new_single_sample(size: ImageSize, format: &'static Format) -> Self { ImageSpec { format, size, sample_count: vk::SampleCountFlags::TYPE_1 } } pub const fn get_size(&self) -> ImageSize { self.size } pub const fn borrow_size(&self) -> &ImageSize { &self.size } pub const fn get_format(&self) -> &'static Format { self.format } pub const fn get_sample_count(&self) -> ash::vk::SampleCountFlags { self.sample_count } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct ImageSubresourceRange { pub aspect_mask: ash::vk::ImageAspectFlags, pub base_mip_level: u32, pub mip_level_count: u32, pub base_array_layer: u32, pub array_layer_count: u32, } impl ImageSubresourceRange { pub fn full_color() -> Self { Self { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, mip_level_count: vk::REMAINING_MIP_LEVELS, base_array_layer: 0, array_layer_count: vk::REMAINING_ARRAY_LAYERS, } } pub const fn as_vk_subresource_range(&self) -> vk::ImageSubresourceRange { vk::ImageSubresourceRange { aspect_mask: self.aspect_mask, base_mip_level: self.base_mip_level, level_count: self.mip_level_count, base_array_layer: self.base_array_layer, layer_count: self.array_layer_count } } } impl From for ash::vk::ImageSubresourceRange { fn from(src: ImageSubresourceRange) -> Self { ash::vk::ImageSubresourceRange::builder() .aspect_mask(src.aspect_mask) .base_mip_level(src.base_mip_level) .base_array_layer(src.base_array_layer) .layer_count(src.array_layer_count) .build() } } /// Contains a description for a vulkan image. /// /// This only contains static information relevant to vulkan (i.e. size or supported usage flags). #[non_exhaustive] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct ImageDescription { pub spec: ImageSpec, pub usage_flags: vk::ImageUsageFlags, } impl ImageDescription { pub fn new_simple(spec: ImageSpec, usage: vk::ImageUsageFlags) -> Self { Self{ spec, usage_flags: usage } } } /// Contains a description for a vulkan image view. /// /// This only contains static information relevant to vulkan (i.e. range or format, however not the /// source image as image views with different sources may have the same description). #[non_exhaustive] #[derive(Copy, Clone, Debug)] pub struct ImageViewDescription { pub view_type: vk::ImageViewType, pub format: &'static Format, pub components: vk::ComponentMapping, pub subresource_range: ImageSubresourceRange, } impl ImageViewDescription { /// Creates a image view description with identity component mapping and subresource range /// covering all mip levels and array layers. pub fn make_full(view_type: vk::ImageViewType, format: &'static Format, aspect_mask: vk::ImageAspectFlags) -> Self { Self { view_type, format, components: vk::ComponentMapping { r: vk::ComponentSwizzle::IDENTITY, g: vk::ComponentSwizzle::IDENTITY, b: vk::ComponentSwizzle::IDENTITY, a: vk::ComponentSwizzle::IDENTITY }, subresource_range: ImageSubresourceRange { aspect_mask, base_mip_level: 0, mip_level_count: vk::REMAINING_MIP_LEVELS, base_array_layer: 0, array_layer_count: vk::REMAINING_ARRAY_LAYERS, } } } /// Creates a image view description with identity component mapping pub fn make_range(view_type: vk::ImageViewType, format: &'static Format, subresource_range: ImageSubresourceRange) -> Self { Self { view_type, format, components: vk::ComponentMapping { r: vk::ComponentSwizzle::IDENTITY, g: vk::ComponentSwizzle::IDENTITY, b: vk::ComponentSwizzle::IDENTITY, a: vk::ComponentSwizzle::IDENTITY }, subresource_range } } } pub struct ImageInstanceData { handle: vk::Image } impl ImageInstanceData { pub fn new(handle: vk::Image) -> Self { Self { handle } } pub unsafe fn get_handle(&self) -> vk::Image { self.handle } } pub struct ImageViewInstanceData { handle: vk::ImageView, source_image: crate::vk::objects::types::ImageId, } impl ImageViewInstanceData { pub fn new(handle: vk::ImageView, source_image: crate::vk::objects::types::ImageId) -> Self { Self { handle, source_image, } } pub fn get_source_image(&self) -> crate::vk::objects::types::ImageId { self.source_image } pub unsafe fn get_handle(&self) -> vk::ImageView { self.handle } } ================================================ FILE: core/natives/src/vk/objects/mod.rs ================================================ pub use buffer::BufferRange; pub use buffer::BufferSpec; pub use crate::util::format::Format; pub use image::ImageDescription; pub use image::ImageSize; pub use image::ImageSpec; pub use image::ImageSubresourceRange; pub use image::ImageViewDescription; pub mod image; pub mod buffer; pub mod types; pub mod swapchain; pub mod surface; ================================================ FILE: core/natives/src/vk/objects/surface.rs ================================================ use std::ffi::CString; use std::fmt::{Debug, Formatter}; use std::ops::Deref; use ash::vk; use crate::prelude::*; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SurfaceId(UUID); impl SurfaceId { pub fn new() -> Self { Self(UUID::new()) } pub fn from_raw(id: UUID) -> Self { Self(id) } pub fn as_uuid(&self) -> UUID { self.0 } } impl Deref for SurfaceId { type Target = UUID; fn deref(&self) -> &Self::Target { &self.0 } } impl From for UUID { fn from(id: SurfaceId) -> Self { id.0 } } impl Debug for SurfaceId { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("SurfaceId({:#016X})", self.0.get_raw())) } } #[derive(Debug)] pub enum SurfaceInitError { /// A vulkan error Vulkan(vk::Result), /// A generic error with attached message Message(String), /// A generic error Generic(), } impl From for SurfaceInitError { fn from(res: vk::Result) -> Self { SurfaceInitError::Vulkan(res) } } pub trait SurfaceProvider: Send + Sync { fn get_required_instance_extensions(&self) -> Vec; fn init(&mut self, entry: &ash::Entry, instance: &ash::Instance) -> Result; fn get_handle(&self) -> Option; } pub struct SurfaceCapabilities { presentable_queues: Box<[u32]>, surface_formats: Box<[vk::SurfaceFormatKHR]>, present_modes: Box<[vk::PresentModeKHR]>, capabilities: vk::SurfaceCapabilitiesKHR, } impl SurfaceCapabilities { pub fn new(instance: &InstanceContext, physical_device: vk::PhysicalDevice, surface: vk::SurfaceKHR) -> Option { let surface_fn = instance.surface_khr()?; let family_count = unsafe { instance.vk().get_physical_device_queue_family_properties(physical_device).len() } as u32; let presentable_queues = (0..family_count).filter(|family| unsafe { surface_fn.get_physical_device_surface_support(physical_device, *family, surface).unwrap() }).collect::>().into_boxed_slice(); if presentable_queues.len() == 0 { return None; } let capabilities = unsafe { surface_fn.get_physical_device_surface_capabilities(physical_device, surface) }.ok()?; let surface_formats = unsafe { surface_fn.get_physical_device_surface_formats(physical_device, surface) }.ok()?.into_boxed_slice(); let present_modes = unsafe { surface_fn.get_physical_device_surface_present_modes(physical_device, surface) }.ok()?.into_boxed_slice(); Some(Self{ presentable_queues, surface_formats, present_modes, capabilities, }) } pub fn get_capabilities(&self) -> &vk::SurfaceCapabilitiesKHR { &self.capabilities } pub fn get_presentable_queue_families(&self) -> &[u32] { self.presentable_queues.as_ref() } pub fn get_surface_formats(&self) -> &[vk::SurfaceFormatKHR] { self.surface_formats.as_ref() } pub fn get_present_modes(&self) -> &[vk::PresentModeKHR] { self.present_modes.as_ref() } } ================================================ FILE: core/natives/src/vk/objects/swapchain.rs ================================================ use crate::util::format::*; use super::image::*; use ash::vk; #[derive(Copy, Clone)] pub struct SwapchainImageSpec { pub format: &'static Format, pub color_space: vk::ColorSpaceKHR, pub extent: vk::Extent2D, pub array_layers: u32, } impl SwapchainImageSpec { pub const fn make(format: &'static Format, color_space: vk::ColorSpaceKHR, width: u32, height: u32) -> Self { Self { format, color_space, extent: vk::Extent2D { width, height }, array_layers: 1 } } pub const fn make_extent(format: &'static Format, color_space: vk::ColorSpaceKHR, extent: vk::Extent2D) -> Self { Self { format, color_space, extent, array_layers: 1 } } pub const fn make_multiview(format: &'static Format, color_space: vk::ColorSpaceKHR, width: u32, height: u32, array_layers: u32) -> Self { Self { format, color_space, extent: vk::Extent2D { width, height }, array_layers } } pub const fn make_multiview_extent(format: &'static Format, color_space: vk::ColorSpaceKHR, extent: vk::Extent2D, array_layers: u32) -> Self { Self { format, color_space, extent, array_layers } } pub const fn get_image_size(&self) -> ImageSize { ImageSize::make_2d_array(self.extent.width, self.extent.height, self.array_layers) } pub const fn as_image_spec(&self) -> ImageSpec { ImageSpec::new(self.get_image_size(), self.format, vk::SampleCountFlags::TYPE_1) } } #[derive(Copy, Clone)] #[non_exhaustive] pub struct SwapchainCreateDesc { pub min_image_count: u32, pub image_spec: SwapchainImageSpec, pub usage: vk::ImageUsageFlags, pub pre_transform: vk::SurfaceTransformFlagsKHR, pub composite_alpha: vk::CompositeAlphaFlagsKHR, pub present_mode: vk::PresentModeKHR, pub clipped: bool, } impl SwapchainCreateDesc { pub fn make(image_spec: SwapchainImageSpec, min_image_count: u32, usage: vk::ImageUsageFlags, present_mode: vk::PresentModeKHR) -> Self { SwapchainCreateDesc { min_image_count, image_spec, usage, pre_transform: vk::SurfaceTransformFlagsKHR::IDENTITY, composite_alpha: vk::CompositeAlphaFlagsKHR::OPAQUE, present_mode, clipped: false, } } } pub struct SwapchainInstanceData { handle: vk::SwapchainKHR, } impl SwapchainInstanceData { pub fn new(handle: vk::SwapchainKHR) -> Self { Self { handle, } } pub unsafe fn get_handle(&self) -> vk::SwapchainKHR { self.handle } } ================================================ FILE: core/natives/src/vk/objects/types.rs ================================================ use std::fmt::{Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::num::NonZeroU64; use std::sync::atomic::{AtomicU64, Ordering}; /// An identifier for object sets #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ObjectSetId(NonZeroU64); static NEXT_OBJECT_SET_ID : AtomicU64 = AtomicU64::new(1); impl ObjectSetId { const OBJECT_SET_ID_MAX : u64 = (1u64 << 40u32) - 1u64; /// Creates a new unique object set id pub fn new() -> Self { let next = NEXT_OBJECT_SET_ID.fetch_add(1, Ordering::Relaxed); if next > Self::OBJECT_SET_ID_MAX { panic!("ObjectSetId overflow"); } Self(NonZeroU64::new(next).unwrap()) } fn from_raw(raw: u64) -> Self { if raw > Self::OBJECT_SET_ID_MAX { panic!("Value passed to ObjectSetId::from_raw is out of bounds"); } Self(NonZeroU64::new(raw).unwrap()) } /// Returns the raw 64bit value of the id pub fn get_raw(&self) -> u64 { self.0.get() } } impl Debug for ObjectSetId { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("ObjectSetId({:#010X})", self.0.get())) } } pub struct ObjectType; // TODO Note this should be updated to a enum once adt_const_params is stabilized. impl ObjectType { pub const fn as_str(ty: u8) -> &'static str { match ty { Self::BUFFER => "Buffer", Self::BUFFER_VIEW => "BufferView", Self::IMAGE => "Image", Self::IMAGE_VIEW => "ImageView", Self::SEMAPHORE => "Semaphore", Self::EVENT => "Event", Self::FENCE => "Fence", Self::SURFACE => "Surface", Self::SWAPCHAIN => "Swapchain", _ => "Invalid", } } pub const GENERIC: u8 = u8::MAX; pub const BUFFER: u8 = 1u8; pub const BUFFER_VIEW: u8 = 2u8; pub const IMAGE: u8 = 3u8; pub const IMAGE_VIEW: u8 = 4u8; pub const SEMAPHORE: u8 = 5u8; pub const EVENT: u8 = 6u8; pub const FENCE: u8 = 7u8; pub const SURFACE: u8 = 8u8; pub const SWAPCHAIN: u8 = 9u8; } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct ObjectId(NonZeroU64); impl ObjectId { const SET_ID_BITS: u32 = 40u32; const SET_ID_OFFSET: u32 = 0u32; const SET_ID_MAX: u64 = (1u64 << Self::SET_ID_BITS) - 1u64; const SET_ID_MASK: u64 = Self::SET_ID_MAX << Self::SET_ID_OFFSET; const INDEX_OFFSET: u32 = 48u32; const INDEX_MAX: u64 = u16::MAX as u64; const INDEX_MASK: u64 = Self::INDEX_MAX << Self::INDEX_OFFSET; const TYPE_OFFSET: u32 = 40u32; const TYPE_MAX: u64 = u8::MAX as u64; const TYPE_MASK: u64 = Self::TYPE_MAX << Self::TYPE_OFFSET; fn make(set_id: ObjectSetId, index: u16, object_type: u8) -> Self { let id = (set_id.get_raw() << Self::SET_ID_OFFSET) | ((index as u64) << Self::INDEX_OFFSET) | ((object_type as u64) << Self::TYPE_OFFSET); Self(NonZeroU64::new(id).unwrap()) } pub fn get_set_id(&self) -> ObjectSetId { ObjectSetId::from_raw((self.0.get() & Self::SET_ID_MASK) >> Self::SET_ID_OFFSET) } pub const fn get_index(&self) -> u16 { ((self.0.get() & Self::INDEX_MASK) >> Self::INDEX_OFFSET) as u16 } pub const fn get_type(&self) -> u8 { ((self.0.get() & Self::TYPE_MASK) >> Self::TYPE_OFFSET) as u8 } /// Converts the id to a generic id pub const fn as_generic(&self) -> ObjectId<{ ObjectType::GENERIC }> { ObjectId::<{ ObjectType::GENERIC }>(self.0) } } impl ObjectId<{ ObjectType::GENERIC }> { /// Attempts to cast a generic object id to a specific type. If the generic id is not of the /// correct type `None` is returned. pub const fn downcast(self) -> Option> { if self.get_type() == TRG { Some(ObjectId::(self.0)) } else { None } } } impl Debug for ObjectId { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("ObjectId(Set: {:#010X}, Index: {:#04X}, Type: {})", self.get_set_id().get_raw(), self.get_index(), self.get_type())) } } impl Hash for ObjectId { fn hash(&self, state: &mut H) { self.0.hash(state) } } pub trait ObjectIdType { type InstanceInfo; fn as_generic(&self) -> GenericId; } pub trait UnwrapToInstanceData<'a, I> { fn unwrap(self) -> &'a I; } pub type GenericId = ObjectId<{ ObjectType::GENERIC }>; pub type BufferId = ObjectId<{ ObjectType::BUFFER }>; pub type BufferViewId = ObjectId<{ ObjectType::BUFFER_VIEW }>; pub type ImageId = ObjectId<{ ObjectType::IMAGE }>; pub type ImageViewId = ObjectId<{ ObjectType::IMAGE_VIEW }>; pub type SemaphoreId = ObjectId<{ ObjectType::SEMAPHORE }>; pub type EventId = ObjectId<{ ObjectType::EVENT }>; pub type FenceId = ObjectId<{ ObjectType::FENCE }>; pub type SurfaceId = ObjectId<{ ObjectType::SURFACE }>; pub type SwapchainId = ObjectId<{ ObjectType::SWAPCHAIN }>; ================================================ FILE: core/natives/src/vk/test.rs ================================================ use std::ffi::{CString}; use std::sync::Arc; use ash::vk; use crate::{B4D_CORE_VERSION_MAJOR, B4D_CORE_VERSION_MINOR, B4D_CORE_VERSION_PATCH}; use crate::device::init::{create_device, DeviceCreateConfig}; use crate::instance::init::{create_instance, InstanceCreateConfig}; use crate::prelude::*; pub fn make_headless_instance() -> Arc { let mut config = InstanceCreateConfig::new( CString::new("B4D Tests").unwrap(), vk::make_api_version(0, B4D_CORE_VERSION_MAJOR, B4D_CORE_VERSION_MINOR, B4D_CORE_VERSION_PATCH) ); config.enable_validation(); // The LunarG desktop profile requires the swapchain extension which in turn requires the surface extensions config.require_surface_khr(); create_instance(config).unwrap() } pub fn make_headless_instance_device() -> (Arc, Arc) { let instance = make_headless_instance(); let mut config = DeviceCreateConfig::new(); config.disable_robustness(); // We do this in b4d so we should use it for our tests as well let device = create_device(config, instance.clone()).unwrap(); (instance, device) } ================================================ FILE: core/natives/src/window.rs ================================================ use std::ffi::{CStr, CString}; use ash::{Entry, Instance, vk}; use winit::dpi::LogicalSize; use winit::event_loop::EventLoop; use winit::window::WindowBuilder; use crate::vk::objects::surface::{SurfaceInitError, SurfaceProvider}; pub struct WinitWindow { handle: winit::window::Window, ash_surface: Option, khr_surface: Option, } impl WinitWindow { pub fn new(title: &str, width: f64, height: f64, event_loop: &EventLoop) -> Self { let window = WindowBuilder::new() .with_title(title) .with_inner_size(LogicalSize::new(width, height)) .build(&event_loop) .unwrap(); window.set_visible(true); Self { handle: window, ash_surface: None, khr_surface: None, } } } impl SurfaceProvider for WinitWindow { fn get_required_instance_extensions(&self) -> Vec { ash_window::enumerate_required_extensions(&self.handle).unwrap().into_iter().map(|str| { CString::from(unsafe { CStr::from_ptr(*str) }) }).collect() } fn init(&mut self, entry: &Entry, instance: &Instance) -> Result { let surface = unsafe { ash_window::create_surface(entry, instance, &self.handle, None)? }; self.khr_surface = Some(surface); self.ash_surface = Some(ash::extensions::khr::Surface::new(entry, instance)); Ok(surface) } fn get_handle(&self) -> Option { self.khr_surface } } impl Drop for WinitWindow { fn drop(&mut self) { if let Some(surface) = self.khr_surface.take() { let khr = self.ash_surface.take().unwrap(); unsafe { khr.destroy_surface(surface, None) }; } } } ================================================ FILE: core/natives/tests/test_common/mod.rs ================================================ ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradle.properties ================================================ org.gradle.jvmargs=-Xmx2G ================================================ FILE: gradlew ================================================ #!/bin/sh # # Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################################## # # Gradle start up script for POSIX generated by Gradle. # # Important for running: # # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole # command line, like: # # ksh Gradle # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; # * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # # (2) This script targets any POSIX shell, so it avoids extensions provided # by Bash, Ksh, etc; in particular arrays are avoided. # # The "traditional" practice of packing multiple parameters into a # space-separated string is a well documented source of bugs and security # problems, so this is (mostly) avoided, by progressively accumulating # options in "$@", and eventually passing that to Java. # # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # see the in-line comments for details. # # There are tweaks for specific operating systems such as AIX, CygWin, # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. # ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link app_path=$0 # Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( *) app_path=$APP_HOME$link ;; esac done APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { echo "$*" } >&2 die () { echo echo "$*" echo exit 1 } >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line # * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) # Now convert the arguments - kludge to limit ourselves to /bin/sh for arg do if case $arg in #( -*) false ;; # don't mess with options #( /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath [ -e "$t" ] ;; #( *) false ;; esac then arg=$( cygpath --path --ignore --mixed "$arg" ) fi # Roll the args list around exactly as many times as the number of # args, so each arg winds up back in the position where it started, but # possibly modified. # # NB: a `for` loop captures its iteration list before it begins, so # changing the positional parameters here affects neither the number of # iterations, nor the values presented in `arg`. shift # remove old arg set -- "$@" "$arg" # push replacement arg done fi # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in # double quotes to make sure that they get re-expanded; and # * put everything else in single quotes, so that it's not re-expanded. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ org.gradle.wrapper.GradleWrapperMain \ "$@" # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # # In Bash we could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # # but POSIX shell has neither arrays nor command substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap # the whole thing up as a single "set" statement. # # This will of course break if any of these variables contains a newline or # an unmatched quote. # eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | xargs -n1 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | tr '\n' ' ' )" '"$@"' exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: mod/.gitignore ================================================ # Gradle local files /build/ # Minecraft run directory /run/ ================================================ FILE: mod/LICENSE ================================================ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ================================================ FILE: mod/build.gradle.kts ================================================ plugins { id("fabric-loom") version "0.12-SNAPSHOT" //id("io.github.juuxel.loom-quiltflower-mini") version "1.2.1" `maven-publish` } group = "graphics.kiln" version = "1.0.0-SNAPSHOT" repositories { mavenCentral() maven { name = "Sonatype Snapshots" url = uri("https://oss.sonatype.org/content/repositories/snapshots/") } maven { name = "ldtteam" url = uri("https://ldtteam.jfrog.io/artifactory/parchmentmc-public/") } } dependencies { minecraft("net.minecraft", "minecraft", properties["minecraft_version"].toString()) mappings(loom.layered { officialMojangMappings() }) modImplementation("net.fabricmc", "fabric-loader", properties["loader_version"].toString()) implementation(project(":core:api")) testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") } base { archivesBaseName = "blaze4d" } loom { accessWidenerPath.set(file("src/main/resources/blaze4d.aw")) runs { val client by this client.vmArgs.add("--add-modules=jdk.incubator.foreign") client.vmArgs.add("--enable-native-access=ALL-UNNAMED") create("clientWithValidation") { inherit(client) configName = "Minecraft Client with Validation Layers" vmArgs.add("-Db4d.enable_validation") } create("clientWithRenderdoc") { inherit(client) configName = "Minecraft Client with Renderdoc" vmArgs.add("-Db4d.enable_renderdoc") } create("clientWithValidationRenderdoc") { inherit(client) configName = "Minecraft Client with Validation Layers and Renderdoc" vmArgs.add("-Db4d.enable_validation") vmArgs.add("-Db4d.enable_renderdoc") } } } tasks { test { useJUnitPlatform() } withType { options.encoding = "UTF-8" options.release.set(18) } withType { from(file("LICENSE")) } processResources { inputs.property("version", project.version) filesMatching("fabric.mod.json") { expand("version" to project.version) } } } publishing { publications { create("mod") { from(components["java"]) pom { name.set("Rosella") packaging = "jar" description.set("A Minecraft mod to use Vulkan through the Rosella engine") url.set("https://github.com/KilnGraphics/Blaze4D") licenses { license { name.set("GNU Lesser General Public License v3.0") url.set("https://www.gnu.org/licenses/lgpl-3.0.txt") } } developers { developer { id.set("hYdos") name.set("Hayden V") email.set("haydenv06@gmail.com") url.set("https://hydos.cf/") } developer { id.set("OroArmor") name.set("Eli Orona") email.set("eliorona@live.com") url.set("https://oroarmor.com/") } developer { id.set("CodingRays") url.set("https://github.com/CodingRays") } developer { id.set("burgerdude") name.set("Ryan G") url.set("https://github.com/burgerguy") } developer { id.set("ramidzkh") email.set("ramidzkh@gmail.com") url.set("https://github.com/ramidzkh") } } } } } repositories { maven { val releasesRepoUrl = uri("${buildDir}/repos/releases") val snapshotsRepoUrl = uri("${buildDir}/repos/snapshots") name = "Project" url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl } maven { val releasesRepoUrl = uri("https://maven.hydos.cf/releases") val snapshotsRepoUrl = uri("https://maven.hydos.cf/snapshots") name = "hydos" url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl val u = System.getenv("MAVEN_USERNAME") ?: return@maven val p = System.getenv("MAVEN_PASSWORD") ?: return@maven credentials { username = u password = p } } } } ================================================ FILE: mod/gradle.properties ================================================ # Fabric Properties # check these on https://fabricmc.net/use minecraft_version=1.19 loader_version=0.14.6 # Mod Properties mod_version=1.0.0 maven_group=me.hydos # publishing modrinth_id=null curseforge_id=null game_versions=1.19 ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/Blaze4D.java ================================================ package graphics.kiln.blaze4d; import com.mojang.blaze3d.systems.RenderSystem; import graphics.kiln.blaze4d.core.Blaze4DCore; import graphics.kiln.blaze4d.core.Frame; import graphics.kiln.blaze4d.core.GlobalImage; import graphics.kiln.blaze4d.core.GlobalMesh; import graphics.kiln.blaze4d.core.natives.Natives; import graphics.kiln.blaze4d.core.types.B4DMeshData; import graphics.kiln.blaze4d.core.types.B4DUniformData; import net.fabricmc.api.ClientModInitializer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.StringFormatterMessageFactory; public class Blaze4D implements ClientModInitializer { public static final Logger LOGGER = LogManager.getLogger("Blaze4D", new StringFormatterMessageFactory()); public static Blaze4DCore core; public static Frame currentFrame; public static GlobalImage boundTexture = null; public static boolean depthWriteEnable = true; public static long glfwWindow; public static void pushUniform(long shaderId, B4DUniformData data) { if(currentFrame != null) { currentFrame.updateUniform(shaderId, data); } else { LOGGER.warn("Updated uniform outside of frame"); } } public static Integer uploadImmediate(B4DMeshData data) { if(currentFrame != null) { return currentFrame.uploadImmediate(data); } else { LOGGER.warn("Attempted to draw outside of frame"); return null; } } public static void drawImmediate(int meshId, long shaderId) { if(currentFrame != null) { currentFrame.drawImmediate(meshId, shaderId, depthWriteEnable); } else { LOGGER.warn("Attempted to draw outside of frame"); } } public static void drawGlobal(GlobalMesh mesh, long shaderId) { if(currentFrame != null) { currentFrame.drawGlobal(mesh, shaderId, depthWriteEnable); } else { LOGGER.warn("Attempted to draw outside of frame"); } } @Override public void onInitializeClient() { Natives.verifyInit(); if(System.getProperty("b4d.enable_renderdoc") != null) { System.loadLibrary("renderdoc"); } } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/Blaze4DMixinPlugin.java ================================================ package graphics.kiln.blaze4d; import net.fabricmc.loader.api.FabricLoader; import org.objectweb.asm.tree.ClassNode; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; import java.util.List; import java.util.Set; public class Blaze4DMixinPlugin implements IMixinConfigPlugin { private static final boolean DEVELOPMENT_ENVIRONMENT = FabricLoader.getInstance().isDevelopmentEnvironment(); @Override public void onLoad(String mixinPackage) { } @Override public String getRefMapperConfig() { return null; } @Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { if (!DEVELOPMENT_ENVIRONMENT && mixinClassName.endsWith("_Development")) { return false; } return !(DEVELOPMENT_ENVIRONMENT && mixinClassName.endsWith("_Runtime")); } @Override public void acceptTargets(Set myTargets, Set otherTargets) { } @Override public List getMixins() { return null; } @Override public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { } @Override public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/Blaze4DPreLaunch.java ================================================ package graphics.kiln.blaze4d; import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint; import org.lwjgl.system.Configuration; import org.lwjgl.system.Platform; import org.lwjgl.system.jemalloc.JEmalloc; import oshi.SystemInfo; public class Blaze4DPreLaunch implements PreLaunchEntrypoint { public static final boolean DEBUG_MEMORY_ENABLED = false; public static final int LWJGL_STACK_SIZE = 65535; // 64mb instead of default 64kb. @Override public void onPreLaunch() { // Configuration.DEBUG_MEMORY_ALLOCATOR.set(DEBUG_MEMORY_ENABLED); // Configuration.STACK_SIZE.set(LWJGL_STACK_SIZE); // // // jemalloc has a memory leak bug on Windows from 5.0.0 to 5.2.0 // if (Platform.get().equals(Platform.WINDOWS) && JEmalloc.JEMALLOC_VERSION_MAJOR == 5 && // (JEmalloc.JEMALLOC_VERSION_MINOR >= 0 && JEmalloc.JEMALLOC_VERSION_MINOR < 2) || (JEmalloc.JEMALLOC_VERSION_MINOR == 2 && JEmalloc.JEMALLOC_VERSION_BUGFIX < 1)) { // Configuration.MEMORY_ALLOCATOR.set("system"); // } // // if (new SystemInfo().getHardware().getProcessor().getProcessorIdentifier().getName().contains("AMD")) { // System.setProperty("rosella:xxhash_with_seed", "true"); // } } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/api/B4DShader.java ================================================ package graphics.kiln.blaze4d.api; import com.mojang.math.Vector3f; public interface B4DShader { long b4dGetShaderId(); } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/api/B4DUniform.java ================================================ package graphics.kiln.blaze4d.api; public interface B4DUniform { graphics.kiln.blaze4d.core.types.B4DUniform getB4DUniform(); } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/api/B4DVertexBuffer.java ================================================ package graphics.kiln.blaze4d.api; import graphics.kiln.blaze4d.core.types.B4DMeshData; public interface B4DVertexBuffer { void setImmediateData(Integer data); } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/api/Utils.java ================================================ package graphics.kiln.blaze4d.api; import com.google.common.collect.ImmutableList; import com.mojang.blaze3d.vertex.VertexFormat; import com.mojang.blaze3d.vertex.VertexFormatElement; import graphics.kiln.blaze4d.Blaze4D; import graphics.kiln.blaze4d.core.types.B4DVertexFormat; public class Utils { public static boolean convertVertexFormat(VertexFormat src, B4DVertexFormat dst) { boolean hasPosition = false; dst.initialize(); dst.setStride(src.getVertexSize()); ImmutableList names = src.getElementAttributeNames(); ImmutableList elements = src.getElements(); int currentOffset = 0; for (int i = 0; i < names.size(); i++) { String name = names.get(i); VertexFormatElement element = elements.get(i); switch (name) { case "Position" -> { if (element.getType() == VertexFormatElement.Type.FLOAT) { hasPosition = true; dst.setPosition(currentOffset, vulkanF32Format(element.getCount())); } else { Blaze4D.LOGGER.warn("Vertex format position type is not float. Skipping!"); } } case "Normal" -> dst.setNormal(currentOffset, vulkanNormFormat(element.getType(), element.getCount())); case "Color" -> dst.setColor(currentOffset, vulkanNormFormat(element.getType(), element.getCount())); case "UV0" -> dst.setUV0(currentOffset, vulkanNormFormat(element.getType(), element.getCount())); case "UV1" -> dst.setUV1(currentOffset, vulkanNormFormat(element.getType(), element.getCount())); case "UV2" -> dst.setUV2(currentOffset, vulkanNormFormat(element.getType(), element.getCount())); } currentOffset += element.getByteSize(); } return hasPosition; } public static int vulkanNormFormat(VertexFormatElement.Type type, int componentCount) { switch (type) { case FLOAT -> { return vulkanF32Format(componentCount); } case UBYTE -> { return vulkanU8NormFormat(componentCount); } case BYTE -> { return vulkanI8NormFormat(componentCount); } case USHORT -> { return vulkanU16NormFormat(componentCount); } case SHORT -> { return vulkanI16NormFormat(componentCount); } default -> throw new RuntimeException("32 bit values cannot be normalized"); } } public static int vulkanF32Format(int componentCount) { switch (componentCount) { case 1 -> { return 100; } case 2 -> { return 103; } case 3 -> { return 106; } case 4 -> { return 109; } default -> throw new RuntimeException("Invalid component count " + componentCount); } } public static int vulkanU8NormFormat(int componentCount) { switch (componentCount) { case 1 -> { return 9; } case 2 -> { return 16; } case 3 -> { return 23; } case 4 -> { return 37; } default -> throw new RuntimeException("Invalid component count " + componentCount); } } public static int vulkanI8NormFormat(int componentCount) { switch (componentCount) { case 1 -> { return 10; } case 2 -> { return 17; } case 3 -> { return 24; } case 4 -> { return 38; } default -> throw new RuntimeException("Invalid component count " + componentCount); } } public static int vulkanU16NormFormat(int componentCount) { switch (componentCount) { case 1 -> { return 70; } case 2 -> { return 77; } case 3 -> { return 84; } case 4 -> { return 91; } default -> throw new RuntimeException("Invalid component count " + componentCount); } } public static int vulkanI16NormFormat(int componentCount) { switch (componentCount) { case 1 -> { return 71; } case 2 -> { return 78; } case 3 -> { return 85; } case 4 -> { return 92; } default -> throw new RuntimeException("Invalid component count " + componentCount); } } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/emulation/GLStateTracker.java ================================================ /** * Tracks opengl state for vulkan emulation. */ package graphics.kiln.blaze4d.emulation; import graphics.kiln.blaze4d.core.types.BlendFactor; import graphics.kiln.blaze4d.core.types.BlendOp; import graphics.kiln.blaze4d.core.types.CompareOp; import graphics.kiln.blaze4d.core.types.PipelineConfiguration; public class GLStateTracker { public static GLStateTracker INSTANCE = new GLStateTracker(); private final PipelineConfiguration pipelineConfiguration; public GLStateTracker() { this.pipelineConfiguration = new PipelineConfiguration(); this.pipelineConfiguration.setDepthTestEnable(false); this.pipelineConfiguration.setDepthCompareOp(CompareOp.LESS); this.pipelineConfiguration.setDepthWriteEnable(false); this.pipelineConfiguration.setBlendEnable(false); this.pipelineConfiguration.setBlendColorOp(BlendOp.ADD); this.pipelineConfiguration.setBlendColorSrcFactor(BlendFactor.ONE); this.pipelineConfiguration.setBlendColorDstFactor(BlendFactor.ZERO); this.pipelineConfiguration.setBlendAlphaOp(BlendOp.ADD); this.pipelineConfiguration.setBlendAlphaSrcFactor(BlendFactor.ONE); this.pipelineConfiguration.setBlendAlphaDstFactor(BlendFactor.ZERO); } public PipelineConfiguration getPipelineConfiguration() { return this.pipelineConfiguration; } public void setDepthTest(boolean enable) { this.pipelineConfiguration.setDepthTestEnable(enable); } public void setDepthFunc(int glFunc) { this.setDepthFunc(CompareOp.fromGlDepthFunc(glFunc)); } public void setDepthFunc(CompareOp op) { this.pipelineConfiguration.setDepthCompareOp(op); } public void setDepthMask(boolean enable) { this.pipelineConfiguration.setDepthWriteEnable(enable); } public void setBlendFunc(int srcFunc, int dstFunc) { this.setBlendFunc(BlendFactor.fromGlBlendFunc(srcFunc), BlendFactor.fromGlBlendFunc(dstFunc)); } public void setBlendFunc(BlendFactor src, BlendFactor dst) { this.setBlendFuncSeparate(src, dst, src, dst); } public void setBlendFuncSeparate(int colorSrcFunc, int colorDstFunc, int alphaSrcFunc, int alphaDstFunc) { this.setBlendFuncSeparate(BlendFactor.fromGlBlendFunc(colorSrcFunc), BlendFactor.fromGlBlendFunc(colorDstFunc), BlendFactor.fromGlBlendFunc(alphaSrcFunc), BlendFactor.fromGlBlendFunc(alphaDstFunc)); } public void setBlendFuncSeparate(BlendFactor colorSrc, BlendFactor colorDst, BlendFactor alphaSrc, BlendFactor alphaDst) { this.pipelineConfiguration.setBlendColorSrcFactor(colorSrc); this.pipelineConfiguration.setBlendColorDstFactor(colorDst); this.pipelineConfiguration.setBlendAlphaSrcFactor(alphaSrc); this.pipelineConfiguration.setBlendAlphaDstFactor(alphaDst); } public void setBlendEquation(int glEquation) { this.setBlendEquation(BlendOp.fromGlBlendEquation(glEquation)); } public void setBlendEquation(BlendOp op) { this.pipelineConfiguration.setBlendColorOp(op); this.pipelineConfiguration.setBlendAlphaOp(op); } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/integration/FramebufferMixin.java ================================================ package graphics.kiln.blaze4d.mixin.integration; import com.mojang.blaze3d.pipeline.RenderTarget; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(RenderTarget.class) public class FramebufferMixin { /* @Shadow public int width; @Shadow public int height; @Shadow public int viewWidth; @Shadow public int viewHeight; @Inject(method = "resize", at = @At("HEAD"), cancellable = true) private void resizingBadAndWorst(int width, int height, boolean getError, CallbackInfo ci) { this.width = width; this.height = height; this.viewWidth = width; this.viewHeight = height; ci.cancel(); } @Inject(method = "_blitToScreen", at = @At("HEAD"), cancellable = true) private void weDontSupportFbosAtm(int width, int height, boolean disableBlend, CallbackInfo ci) { ci.cancel(); } @Inject(method = "clear", at = @At("HEAD"), cancellable = true) private void thisMessesUpSkyColor(boolean clearError, CallbackInfo ci) { ci.cancel(); }*/ } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/integration/GLXMixin.java ================================================ package graphics.kiln.blaze4d.mixin.integration; import com.mojang.blaze3d.platform.GLX; import com.mojang.blaze3d.platform.GlStateManager; import graphics.kiln.blaze4d.Blaze4D; import org.lwjgl.glfw.GLFW; import org.lwjgl.system.APIUtil; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.util.function.LongSupplier; @Mixin(value = GLX.class, remap = false) public class GLXMixin { // // @Inject(method = "getOpenGLVersionString", at = @At("HEAD"), cancellable = true) // private static void getVulkanInfoString(CallbackInfoReturnable cir) { // cir.setReturnValue(Blaze4D.rosella == null || Blaze4D.rosella.common.device == null ? "NO CONTEXT" : GlStateManager._getString(7937) + " " + GlStateManager._getString(7938) + ", " + GlStateManager._getString(7936)); // } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/integration/GlDebugMixin.java ================================================ package graphics.kiln.blaze4d.mixin.integration; import com.mojang.blaze3d.platform.GlDebug; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(GlDebug.class) public class GlDebugMixin { /* @Inject(method = "enableDebugCallback", at = @At("HEAD"), cancellable = true) private static void debuggingIsForTheWeak(int verbosity, boolean sync, CallbackInfo ci) { ci.cancel(); } */ } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/integration/GlStateManagerMixin.java ================================================ package graphics.kiln.blaze4d.mixin.integration; import com.mojang.blaze3d.platform.GlStateManager; //import com.mojang.blaze3d.systems.RenderSystem; //import com.sun.jna.platform.win32.GL; //import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; //import org.jetbrains.annotations.Nullable; //import org.lwjgl.opengl.GL13; //import org.lwjgl.system.MemoryStack; //import org.lwjgl.vulkan.VK10; //import org.lwjgl.vulkan.VkExtensionProperties; //import org.lwjgl.vulkan.VkPhysicalDevice; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.util.Map; import java.util.stream.Collectors; //import static org.lwjgl.vulkan.VK10.vkEnumerateDeviceExtensionProperties; /** * Buffer Implementation just in case mods use it * TODO: this implementation can stay just in case, but i want to make IndexBuffer in RenderSystem work properly */ @Mixin(value = GlStateManager.class, remap = false) public class GlStateManagerMixin { // // private static final Map BUFFER_MAP = new Object2ObjectLinkedOpenHashMap<>(); // private static int NEXT_BUFFER_ID = 1; // // /** // * @author Blaze4D // * @reason to implement buffers // */ // @Overwrite // public static void _glBindBuffer(int target, int buffer) { // RenderSystem.assertOnGameThreadOrInit(); // } // // /** // * @author Blaze4D // * @reason to implement buffers // */ // @Overwrite // public static int _glGenBuffers() { // RenderSystem.assertOnGameThreadOrInit(); // return NEXT_BUFFER_ID++; // } // // /** // * @author Blaze4D // * @reason to implement buffers // */ // @Overwrite // public static void _glBufferData(int target, long size, int usage) { // RenderSystem.assertOnGameThreadOrInit(); // BUFFER_MAP.put(target, ByteBuffer.allocate((int) size)); // } // // /** // * @author Blaze4D // * @reason to implement buffers // */ // @Overwrite // @Nullable // public static ByteBuffer _glMapBuffer(int target, int access) { // RenderSystem.assertOnGameThreadOrInit(); // ByteBuffer buffer = ByteBuffer.allocate(80092); // BUFFER_MAP.put(target, buffer); // return buffer; // } // // /** // * @author Blaze4D // * @reason to implement buffers // */ // @Overwrite // public static void _glUnmapBuffer(int target) { // RenderSystem.assertOnGameThreadOrInit(); // BUFFER_MAP.remove(target).clear(); // } // // @Inject(method = { // "_texParameter(IIF)V", // "_texParameter(III)V", // "_texImage2D", // "_clear", // "_glBindFramebuffer", // "_viewport" // }, at = @At("HEAD"), cancellable = true) // private static void unimplementedGlCalls(CallbackInfo ci) { // //TODO: IMPL // ci.cancel(); // } // // @Inject(method = "_enableColorLogicOp", at = @At("HEAD"), cancellable = true) // private static void enableColorLogicOp(CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setColorLogicOpEnabled(true); // ci.cancel(); // } // // @Inject(method = "_disableColorLogicOp", at = @At("HEAD"), cancellable = true) // private static void disableColorLogicOp(CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setColorLogicOpEnabled(false); // ci.cancel(); // } // // @Inject(method = "_logicOp", at = @At("HEAD"), cancellable = true) // private static void logicOp(int op, CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setColorLogicOp(ConversionUtils.glToVkLogicOp(op)); // ci.cancel(); // } // // @Inject(method = "_enableDepthTest", at = @At("HEAD"), cancellable = true) // private static void enableDepthTest(CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setDepthTestEnabled(true); // ci.cancel(); // } // // @Inject(method = "_disableDepthTest", at = @At("HEAD"), cancellable = true) // private static void disableDepthTest(CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setDepthTestEnabled(false); // ci.cancel(); // } // // @Inject(method = "_enableScissorTest", at = @At("HEAD"), cancellable = true) // private static void enableScissorTest(CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setScissorEnabled(true); // ci.cancel(); // } // // @Inject(method = "_disableScissorTest", at = @At("HEAD"), cancellable = true) // private static void disableScissorTest(CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setScissorEnabled(false); // ci.cancel(); // } // // @Inject(method = "_scissorBox", at = @At("HEAD"), cancellable = true) // private static void scissorBox(int x, int y, int width, int height, CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setScissorX(x); // GlobalRenderSystem.currentStateInfo.setScissorY(y); // GlobalRenderSystem.currentStateInfo.setScissorWidth(width); // GlobalRenderSystem.currentStateInfo.setScissorHeight(height); // ci.cancel(); // } // // @Inject(method = "_enableCull", at = @At("HEAD"), cancellable = true) // private static void enableCull(CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setCullEnabled(true); // ci.cancel(); // } // // @Inject(method = "_disableCull", at = @At("HEAD"), cancellable = true) // private static void disableCull(CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setCullEnabled(false); // ci.cancel(); // } // // @Inject(method = "_depthFunc", at = @At("HEAD"), cancellable = true) // private static void depthFunc(int func, CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setDepthCompareOp(ConversionUtils.glToVkDepthFunc(func)); // ci.cancel(); // } // // @Inject(method = "_enableBlend", at = @At("HEAD"), cancellable = true) // private static void enableBlend(CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setBlendEnabled(true); // ci.cancel(); // } // // @Inject(method = "_disableBlend", at = @At("HEAD"), cancellable = true) // private static void disableBlend(CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setBlendEnabled(false); // ci.cancel(); // } // // @Inject(method = "_enablePolygonOffset", at = @At("HEAD"), cancellable = true) // private static void enablePolygonOffset(CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setDepthBiasEnabled(true); // ci.cancel(); // } // // @Inject(method = "_disablePolygonOffset", at = @At("HEAD"), cancellable = true) // private static void disablePolygonOffset(CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setDepthBiasEnabled(false); // ci.cancel(); // } // // @Inject(method = "_blendEquation", at = @At("HEAD"), cancellable = true) // private static void blendEquation(int mode, CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setBlendOp(ConversionUtils.glToVkBlendOp(mode)); // ci.cancel(); // } // // @Inject(method = "_blendFunc", at = @At("HEAD"), cancellable = true) // private static void blendFunc(int srcFactor, int dstFactor, CallbackInfo ci) { // int vkSrcFactor = ConversionUtils.glToVkBlendFunc(srcFactor); // int vkDstFactor = ConversionUtils.glToVkBlendFunc(dstFactor); // GlobalRenderSystem.currentStateInfo.setSrcColorBlendFactor(vkSrcFactor); // GlobalRenderSystem.currentStateInfo.setDstColorBlendFactor(vkDstFactor); // GlobalRenderSystem.currentStateInfo.setSrcAlphaBlendFactor(vkSrcFactor); // GlobalRenderSystem.currentStateInfo.setDstAlphaBlendFactor(vkDstFactor); // ci.cancel(); // } // // @Inject(method = "_blendFuncSeparate", at = @At("HEAD"), cancellable = true) // private static void blendFunc(int srcFactorRGB, int dstFactorRGB, int srcFactorAlpha, int dstFactorAlpha, CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setSrcColorBlendFactor(ConversionUtils.glToVkBlendFunc(srcFactorRGB)); // GlobalRenderSystem.currentStateInfo.setDstColorBlendFactor(ConversionUtils.glToVkBlendFunc(dstFactorRGB)); // GlobalRenderSystem.currentStateInfo.setSrcAlphaBlendFactor(ConversionUtils.glToVkBlendFunc(srcFactorAlpha)); // GlobalRenderSystem.currentStateInfo.setDstAlphaBlendFactor(ConversionUtils.glToVkBlendFunc(dstFactorAlpha)); // ci.cancel(); // } // // @Inject(method = "_colorMask", at = @At("HEAD"), cancellable = true) // private static void colorMask(boolean red, boolean green, boolean blue, boolean alpha, CallbackInfo ci) { // int colorMask = 0; // if (red) colorMask |= VK10.VK_COLOR_COMPONENT_R_BIT; // if (green) colorMask |= VK10.VK_COLOR_COMPONENT_G_BIT; // if (blue) colorMask |= VK10.VK_COLOR_COMPONENT_B_BIT; // if (alpha) colorMask |= VK10.VK_COLOR_COMPONENT_A_BIT; // GlobalRenderSystem.currentStateInfo.setColorMask(colorMask); // ci.cancel(); // } // // @Inject(method = "_depthMask", at = @At("HEAD"), cancellable = true) // private static void depthMask(boolean mask, CallbackInfo ci) { // GlobalRenderSystem.currentStateInfo.setDepthMask(mask); // ci.cancel(); // } // // @Inject(method = "_bindTexture", at = @At("HEAD"), cancellable = true) // private static void bindTexture(int texId, CallbackInfo ci) { // GlobalRenderSystem.boundTextureIds[GlobalRenderSystem.activeTextureSlot] = texId; // ci.cancel(); // } // // @Inject(method = "_getActiveTexture", at = @At("HEAD"), cancellable = true) // private static void getActiveTexture(CallbackInfoReturnable cir) { // cir.setReturnValue(GlobalRenderSystem.activeTextureSlot + GL13.GL_TEXTURE0); // } // // @Inject(method = "_activeTexture", at = @At("HEAD"), cancellable = true) // private static void activeTexture(int texSlot, CallbackInfo ci) { // GlobalRenderSystem.activeTextureSlot = texSlot - GL13.GL_TEXTURE0; // ci.cancel(); // } // // @Inject(method = "_genTexture", at = @At("HEAD"), cancellable = true) // private static void genTexture(CallbackInfoReturnable cir) { // cir.setReturnValue(Blaze4D.rosella.common.textureManager.generateTextureId()); // } // // @Inject(method = "_deleteTexture", at = @At("HEAD"), cancellable = true) // private static void deleteTexture(int texture, CallbackInfo ci) { // Blaze4D.rosella.common.textureManager.deleteTexture(texture); // ci.cancel(); // } // // @Inject(method = "_genTextures", at = @At("HEAD"), cancellable = true) // private static void genTextures(int[] is, CallbackInfo ci) { // for (int i = 0; i < is.length; i++) { // is[i] = Blaze4D.rosella.common.textureManager.generateTextureId(); // } // ci.cancel(); // } // // @Inject(method = "_deleteTextures", at = @At("HEAD"), cancellable = true) // private static void deleteTextures(int[] is, CallbackInfo ci) { // for (int textureId : is) { // Blaze4D.rosella.common.textureManager.deleteTexture(textureId); // } // ci.cancel(); // } // // @Inject(method = "_clearStencil", at = @At("HEAD"), cancellable = true) // private static void clearStencil(int stencil, CallbackInfo ci) { // Blaze4D.rosella.renderer.lazilyClearStencil(stencil); // TODO: should this value be converted ogl to vk? // ci.cancel(); // } // // @Inject(method = "_clearDepth", at = @At("HEAD"), cancellable = true) // private static void clearDepth(double depth, CallbackInfo ci) { // Blaze4D.rosella.renderer.lazilyClearDepth((float) depth); // ci.cancel(); // } // // @Inject(method = "_polygonMode", at = @At("HEAD"), cancellable = true) // private static void polygonMode(int face, int mode, CallbackInfo ci) { // // TODO: figure out how to have separate polygon modes for front and back // GlobalRenderSystem.currentStateInfo.setPolygonMode(ConversionUtils.glToRosellaPolygonMode(mode)); // ci.cancel(); // } // // @Inject(method = "_polygonOffset", at = @At("HEAD"), cancellable = true) // private static void polygonOffset(float factor, float units, CallbackInfo ci) { // // TODO: figure out clamp and don't make it constant, figure out difference between LINE, POINT, and FILL offset gl stuff // GlobalRenderSystem.currentStateInfo.setDepthBiasConstantFactor(units); // GlobalRenderSystem.currentStateInfo.setDepthBiasSlopeFactor(factor); // ci.cancel(); // } // // @Inject(method = "_getString", at = @At("HEAD"), cancellable = true) // private static void getString(int glStringId, CallbackInfoReturnable ci) { // ci.setReturnValue( // Blaze4D.rosella == null ? "Device not initialized" : // switch (glStringId) { // case GL.GL_VENDOR -> tryParseVendorId(Blaze4D.rosella.common.device.getProperties().vendorID()); // case GL.GL_EXTENSIONS -> getAllExtensionsString(Blaze4D.rosella.common.device.getRawDevice().getPhysicalDevice()); // case GL.GL_RENDERER -> Blaze4D.rosella.common.device.getProperties().deviceNameString(); // case GL.GL_VERSION -> "Vulkan API " + createVersionString(Blaze4D.rosella.common.device.getRawDevice().getCapabilities().apiVersion); // default -> throw new IllegalStateException("Unexpected value: " + glStringId); // } // ); // } // // @Unique // private static String allExtensionsString; // // @Unique // /** // * @param device the device to check // * @return all the supported extensions of the device as a set of strings. // */ // private static String getAllExtensionsString(VkPhysicalDevice device) { // if (allExtensionsString == null) { // try (MemoryStack stack = MemoryStack.stackPush()) { // IntBuffer extensionCount = stack.ints(0); // VkExtensionProperties.Buffer availableExtensions = VkExtensionProperties.calloc(extensionCount.get(0), stack); // ok(vkEnumerateDeviceExtensionProperties(device, (CharSequence) null, extensionCount, availableExtensions)); // allExtensionsString = availableExtensions.stream() // .map(VkExtensionProperties::extensionNameString) // .collect(Collectors.joining(" ")); // } // } // return allExtensionsString; // } // // @Unique // private static String tryParseVendorId(int vendorId) { // return switch (vendorId) { // case 0x10DE -> "NVIDIA Corporation"; // case 0x1002 -> "AMD"; // case 0x8086 -> "INTEL"; // case 0x13B5 -> "ARM"; // case 0x1010 -> "ImgTec"; // case 0x5143 -> "Qualcomm"; // default -> "Vendor unknown. Vendor ID: " + vendorId; // }; // } // // private static String createVersionString(int apiVersion) { // int major = VK10.VK_VERSION_MAJOR(apiVersion); // int minor = VK10.VK_VERSION_MINOR(apiVersion); // int patch = VK10.VK_VERSION_PATCH(apiVersion); // return major + "." + minor + "." + patch; // } // // /** // * @author Blaze4D // * @reason Clear Color Integration // *

// * Minecraft may be regarded as having bad code, but sometimes its ok. // * TODO: use vkCmdClearAttachments after implementing render graph // */ // @Overwrite // public static void _clearColor(float red, float green, float blue, float alpha) { // RenderSystem.assertOnGameThreadOrInit(); // Blaze4D.rosella.renderer.lazilyClearColor(new Color(red, green, blue, alpha)); // } // // /** // * @author Blaze4D // */ // @Overwrite // public static int _glGenVertexArrays() { // return 0; // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void _glBindVertexArray(int i) { // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void _disableVertexAttribArray(int index) { // } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/integration/MinecraftClientMixin.java ================================================ package graphics.kiln.blaze4d.mixin.integration; import graphics.kiln.blaze4d.Blaze4D; import net.minecraft.client.Minecraft; import org.lwjgl.glfw.GLFW; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(Minecraft.class) public class MinecraftClientMixin { @Inject(method = "runTick", at = @At("HEAD")) private void startFrame(CallbackInfo ci) { if (Blaze4D.currentFrame != null) { try { Blaze4D.currentFrame.close(); } catch (Exception ex) { throw new RuntimeException("Failed to end frame", ex); } Blaze4D.currentFrame = null; Blaze4D.LOGGER.warn("Started new frame with running old frame"); } int[] width = new int[1]; int[] height = new int[1]; GLFW.glfwGetWindowSize(Blaze4D.glfwWindow, width, height); Blaze4D.currentFrame = Blaze4D.core.startFrame(width[0], height[0]); } @Inject(method = "runTick", at = @At("RETURN")) private void endFrame(CallbackInfo ci) { if (Blaze4D.currentFrame != null) { try { Blaze4D.currentFrame.close(); } catch (Exception ex) { throw new RuntimeException("Failed to end frame", ex); } Blaze4D.currentFrame = null; } } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/integration/VertexFormatMixin.java ================================================ package graphics.kiln.blaze4d.mixin.integration; import com.mojang.blaze3d.vertex.VertexFormat; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; @Mixin(VertexFormat.IndexType.class) public class VertexFormatMixin { @Overwrite public static VertexFormat.IndexType least(int i) { // We don't support byte indices // if ((i & 0xFFFF0000) != 0) { // return VertexFormat.IndexType.INT; // } // return VertexFormat.IndexType.SHORT; return VertexFormat.IndexType.INT; // For now only ints } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/integration/VideoWarningManagerMixin.java ================================================ package graphics.kiln.blaze4d.mixin.integration; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.util.List; import java.util.regex.Pattern; import net.minecraft.client.renderer.GpuWarnlistManager; @Mixin(GpuWarnlistManager.Preparations.class) public class VideoWarningManagerMixin { // @Inject(method = "matchAny", at = @At("HEAD"), cancellable = true) // private static void whatWarningsAreYouTalkinAbout(List warningPattern, String info, CallbackInfoReturnable cir) { // cir.setReturnValue("warning"); // } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/integration/WindowFramebufferMixin.java ================================================ package graphics.kiln.blaze4d.mixin.integration; import com.mojang.blaze3d.pipeline.MainTarget; import com.mojang.blaze3d.pipeline.RenderTarget; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(MainTarget.class) public class WindowFramebufferMixin /*extends RenderTarget*/ { // public WindowFramebufferMixin(boolean useDepth) { // super(useDepth); // } // // @Inject(method = "allocateColorAttachment", at = @At("HEAD"), cancellable = true) // private void supportAllColours(CallbackInfoReturnable cir) { // cir.setReturnValue(true); // } // // @Inject(method = "allocateDepthAttachment", at = @At("HEAD"), cancellable = true) // private void supportDepth(CallbackInfoReturnable cir) { // cir.setReturnValue(true); // } // // @Inject(method = "createFrameBuffer", at = @At("HEAD"), cancellable = true) // private void fbosAreBad(int width, int height, CallbackInfo ci) { // this.viewWidth = width; // this.viewHeight = height; // this.width = width; // this.height = height; // ci.cancel(); // } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/integration/WindowMixin.java ================================================ package graphics.kiln.blaze4d.mixin.integration; import com.mojang.blaze3d.platform.DisplayData; import com.mojang.blaze3d.platform.ScreenManager; import com.mojang.blaze3d.platform.WindowEventHandler; import graphics.kiln.blaze4d.Blaze4D; import graphics.kiln.blaze4d.core.Blaze4DCore; import org.lwjgl.glfw.GLFW; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(com.mojang.blaze3d.platform.Window.class) public abstract class WindowMixin { // @Shadow // @Final // private static Logger LOGGER; // // @Mutable // @Shadow // @Final // private long window; // // @Inject(method = "bootCrash", at = @At("HEAD"), cancellable = true) // private static void silenceGl(int error, long description, CallbackInfo ci) { // String message = "suppressed GLFW/OpenGL error " + error + ": " + MemoryUtil.memUTF8(description); // LOGGER.warn(message); // ci.cancel(); // } // // @ModifyArg(method = "", at = @At(value = "INVOKE", target = "Lorg/lwjgl/glfw/GLFW;glfwWindowHint(II)V", ordinal = 0, remap = false), index = 1) // private int setNoApi(int initialApi) { // return GLFW.GLFW_NO_API; // } // // @Redirect(method = "", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL;createCapabilities()Lorg/lwjgl/opengl/GLCapabilities;", remap = false)) // private GLCapabilities cancelCreateCapabilities() { // return null; // } // @Inject(method = "", at = @At(value = "TAIL")) private void initializeRosellaWindow(WindowEventHandler eventHandler, ScreenManager monitorTracker, DisplayData settings, String videoMode, String title, CallbackInfo ci) { GLFW.glfwDefaultWindowHints(); GLFW.glfwWindowHint(GLFW.GLFW_CLIENT_API, GLFW.GLFW_NO_API); Blaze4D.glfwWindow = GLFW.glfwCreateWindow(800, 600, "BLAAAAAZEEEE 4DDDDD", 0, 0); Blaze4D.core = new Blaze4DCore(Blaze4D.glfwWindow); Blaze4D.core.setDebugMode(Blaze4DCore.DebugMode.TEXTURED0); // Blaze4D.window = new GlfwWindow.SuppliedGlfwWindow(window); // Blaze4D.rosella = new Rosella(Blaze4D.window, "Blaze4D", Blaze4D.VALIDATION_ENABLED); // Blaze4D.finishSetup(); // // try { // AftermathHandler.initialize(Thread.currentThread()); // } catch (Throwable throwable) { // // We don't really care if this doesn't work, especially outside of development // if (FabricLoader.getInstance().isDevelopmentEnvironment()) { // throwable.printStackTrace(); // } // } } // // @Inject(method = "onFramebufferResize", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/WindowEventHandler;resizeDisplay()V")) // private void hintRendererForRecreation(long window, int width, int height, CallbackInfo ci) { // Blaze4D.rosella.renderer.queueRecreateSwapchain(); // } // // @Inject(method = "updateVsync", at = @At(value = "INVOKE", target = "Lorg/lwjgl/glfw/GLFW;glfwSwapInterval(I)V", remap = false), cancellable = true) // private void setVsync(boolean vsync, CallbackInfo ci) { // boolean previousVsync = Blaze4D.window.doVsync; // if (previousVsync != vsync) { // Blaze4D.window.doVsync = vsync; // Blaze4D.rosella.renderer.queueRecreateSwapchain(); // TODO: move this probably // } // ci.cancel(); // } // // @Inject(method = "setIcon", at = @At(value = "INVOKE", target = "Lorg/lwjgl/glfw/GLFW;glfwSetWindowIcon(JLorg/lwjgl/glfw/GLFWImage$Buffer;)V", remap = false), locals = LocalCapture.CAPTURE_FAILSOFT) // private void setIcon(InputStream icon16, InputStream icon32, CallbackInfo ci, MemoryStack memoryStack, IntBuffer intBuffer, IntBuffer intBuffer2, IntBuffer intBuffer3, GLFWImage.Buffer buffer, ByteBuffer byteBuffer, ByteBuffer byteBuffer2) { // GLFW.glfwSetWindowIcon(window, buffer); // } // // @Inject(method = "close", at = @At("HEAD"), cancellable = true) // private void freeRosella(CallbackInfo ci) { // Callbacks.glfwFreeCallbacks(this.window); //// this.defaultErrorCallback.close(); // // if (Blaze4D.rosella != null) { // Blaze4D.rosella.free(); // Blaze4D.rosella = null; // } // Aftermath.disableGPUCrashDumps(); // ci.cancel(); // } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/render/BufferUploaderMixin.java ================================================ package graphics.kiln.blaze4d.mixin.render; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.BufferUploader; import com.mojang.blaze3d.vertex.VertexBuffer; import com.mojang.blaze3d.vertex.VertexFormat; import graphics.kiln.blaze4d.Blaze4D; import graphics.kiln.blaze4d.api.B4DShader; import graphics.kiln.blaze4d.api.B4DVertexBuffer; import graphics.kiln.blaze4d.core.types.B4DIndexType; import graphics.kiln.blaze4d.core.types.B4DMeshData; import graphics.kiln.blaze4d.core.types.B4DPrimitiveTopology; import org.lwjgl.system.MemoryUtil; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.nio.ByteBuffer; import java.nio.IntBuffer; @Mixin(BufferUploader.class) public class BufferUploaderMixin { @Inject(method = "upload", at = @At("RETURN")) private static void prepareImmediate(BufferBuilder.RenderedBuffer renderedBuffer, CallbackInfoReturnable ci) { if(ci.getReturnValue() != null) { drawImmediateBuffer(renderedBuffer, (B4DVertexBuffer) ci.getReturnValue()); } } private static void drawImmediateBuffer(BufferBuilder.RenderedBuffer renderedBuffer, B4DVertexBuffer target) { B4DShader shader = (B4DShader) RenderSystem.getShader(); if (shader == null) { return; } if (!renderedBuffer.isEmpty()) { try(B4DMeshData b4DMeshData = new B4DMeshData()) { BufferBuilder.DrawState drawState = renderedBuffer.drawState(); b4DMeshData.setVertexStride(drawState.format().getVertexSize()); b4DMeshData.setIndexCount(drawState.indexCount()); b4DMeshData.setPrimitiveTopology(B4DPrimitiveTopology.fromGLMode(drawState.mode().asGLMode)); ByteBuffer vertexData = renderedBuffer.vertexBuffer(); b4DMeshData.setVertexData(MemoryUtil.memAddress(vertexData), vertexData.remaining()); if(drawState.sequentialIndex()) { b4DMeshData.setIndexType(B4DIndexType.UINT32); IntBuffer indexData = generateSequentialIndices(drawState.mode(), drawState.indexCount()); b4DMeshData.setIndexData(MemoryUtil.memAddress(indexData), indexData.remaining() * 4L); } else { if (drawState.indexType() == VertexFormat.IndexType.SHORT) { b4DMeshData.setIndexType(B4DIndexType.UINT16); } else if (drawState.indexType() == VertexFormat.IndexType.INT) { b4DMeshData.setIndexType(B4DIndexType.UINT32); } else { return; } ByteBuffer indexData = renderedBuffer.indexBuffer(); b4DMeshData.setIndexData(MemoryUtil.memAddress(indexData), indexData.remaining()); } Integer id = Blaze4D.uploadImmediate(b4DMeshData); if(id != null) { target.setImmediateData(id); } } catch (Exception e) { throw new RuntimeException(e); } } } private static IntBuffer generateSequentialIndices(VertexFormat.Mode mode, int indexCount) { IntBuffer indices = MemoryUtil.memAllocInt(indexCount); indices.limit(indexCount); switch(mode) { case QUADS -> { for (int i = 0; i < indexCount / 6 * 4; i += 4) { indices.put(i); indices.put(i + 1); indices.put(i + 2); indices.put(i + 2); indices.put(i + 3); indices.put(i); } } default -> { for (int i = 0; i < indexCount; i++) { indices.put(i); } } } return indices.rewind(); } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/render/RenderSystemMixin.java ================================================ package graphics.kiln.blaze4d.mixin.render; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.Tesselator; import graphics.kiln.blaze4d.Blaze4D; import org.apache.commons.compress.harmony.pack200.NewAttributeBands; import org.lwjgl.glfw.GLFW; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(value = RenderSystem.class, remap = false) public class RenderSystemMixin { @Inject(method="depthMask", at=@At("HEAD")) private static void setDepthWrite(boolean enable, CallbackInfo ci) { Blaze4D.depthWriteEnable = enable; } // // @Inject(method = "maxSupportedTextureSize", at = @At("HEAD"), cancellable = true) // private static void setMaxSupportedTextureSize(CallbackInfoReturnable cir) { // cir.setReturnValue(1024 * 1000); // } // // /** // * @author Blaze4D // * @reason Removal Of GL Specific Code // */ // @Overwrite // public static void flipFrame(long window) { // RenderSystem.replayQueue(); // Tesselator.getInstance().getBuilder().clear(); // GLFW.glfwPollEvents(); // GlobalRenderSystem.postDraw(); // } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/render/VertexBufferMixin.java ================================================ package graphics.kiln.blaze4d.mixin.render; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.*; import graphics.kiln.blaze4d.Blaze4D; import graphics.kiln.blaze4d.api.B4DShader; import graphics.kiln.blaze4d.api.B4DVertexBuffer; import graphics.kiln.blaze4d.core.GlobalMesh; import graphics.kiln.blaze4d.core.types.B4DIndexType; import graphics.kiln.blaze4d.core.types.B4DMeshData; import graphics.kiln.blaze4d.core.types.B4DPrimitiveTopology; import org.lwjgl.system.MemoryUtil; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.nio.ByteBuffer; import java.nio.IntBuffer; /** * Turns out, Minecraft uses this class for world rendering. when a part of the world is to be rendered, the buffer will be cleared and replaced with just the sky. this will then be uploaded to a {@link VertexBuffer} and then cleared again for the rest of the game to render. */ @Mixin(VertexBuffer.class) public class VertexBufferMixin implements B4DVertexBuffer { @Final private B4DMeshData meshData = new B4DMeshData(); private GlobalMesh globalMesh = null; private Integer currentImmediate = null; /** * @author Blaze4D * @reason Allow for uploading Vertex Buffers */ @Inject(method="upload", at = @At("HEAD")) private void uploadBuffer(BufferBuilder.RenderedBuffer renderedBuffer, CallbackInfo ci) { if (this.globalMesh != null) { try { this.globalMesh.close(); } catch (Exception e) { throw new RuntimeException(e); } this.globalMesh = null; } if (!renderedBuffer.isEmpty()) { BufferBuilder.DrawState drawState = renderedBuffer.drawState(); this.meshData.setVertexStride(drawState.format().getVertexSize()); this.meshData.setIndexCount(drawState.indexCount()); this.meshData.setPrimitiveTopology(B4DPrimitiveTopology.fromGLMode(drawState.mode().asGLMode)); ByteBuffer vertexData = renderedBuffer.vertexBuffer(); this.meshData.setVertexData(MemoryUtil.memAddress(vertexData), vertexData.remaining()); if(drawState.sequentialIndex()) { this.meshData.setIndexType(B4DIndexType.UINT32); IntBuffer indexData = generateSequentialIndices(drawState.mode(), drawState.indexCount()); this.meshData.setIndexData(MemoryUtil.memAddress(indexData), indexData.remaining() * 4L); } else { if (drawState.indexType() == VertexFormat.IndexType.SHORT) { this.meshData.setIndexType(B4DIndexType.UINT16); } else if (drawState.indexType() == VertexFormat.IndexType.INT) { this.meshData.setIndexType(B4DIndexType.UINT32); } else { return; } ByteBuffer indexData = renderedBuffer.indexBuffer(); this.meshData.setIndexData(MemoryUtil.memAddress(indexData), indexData.remaining()); } this.globalMesh = Blaze4D.core.createGlobalMesh(this.meshData); } } private static IntBuffer generateSequentialIndices(VertexFormat.Mode mode, int indexCount) { IntBuffer indices = MemoryUtil.memAllocInt(indexCount); indices.limit(indexCount); switch(mode) { case QUADS -> { for (int i = 0; i < indexCount / 6 * 4; i += 4) { indices.put(i); indices.put(i + 1); indices.put(i + 2); indices.put(i + 2); indices.put(i + 3); indices.put(i); } } default -> { for (int i = 0; i < indexCount; i++) { indices.put(i); } } } return indices.rewind(); } @Inject(method="draw", at = @At("HEAD")) private void draw(CallbackInfo ci) { if(this.currentImmediate != null) { if (RenderSystem.getShader() != null) { Blaze4D.drawImmediate(this.currentImmediate, ((B4DShader) RenderSystem.getShader()).b4dGetShaderId()); this.currentImmediate = null; } } else if (this.globalMesh != null) { if (RenderSystem.getShader() != null) { Blaze4D.drawGlobal(this.globalMesh, ((B4DShader) RenderSystem.getShader()).b4dGetShaderId()); } } } // // /** // * @author Blaze4D // * @reason Allows rendering things such as the sky. // */ // @Overwrite // public void _drawWithShader(com.mojang.math.Matrix4f mcModelViewMatrix, com.mojang.math.Matrix4f mcProjectionMatrix, ShaderInstance shader) { // GlobalRenderSystem.updateUniforms(shader, mcModelViewMatrix, mcProjectionMatrix); // callWrapperRender(shader); // } // // // @Unique // private void callWrapperRender(ShaderInstance mcShader) { // RawShaderProgram rawProgram = GlobalRenderSystem.SHADER_PROGRAM_MAP.get(mcShader.getId()); // ShaderProgram rosellaShaderProgram = Blaze4D.rosella.common.shaderManager.getOrCreateShader(rawProgram); // wrapper.render(rosellaShaderProgram, GlobalRenderSystem.getShaderUbo(mcShader)); // } // @Inject(method = "close", at = @At("HEAD"), cancellable = true) private void close(CallbackInfo ci) throws Exception { if (this.globalMesh != null) { this.globalMesh.close(); this.globalMesh = null; } this.meshData.close(); } @Override public void setImmediateData(Integer data) { this.currentImmediate = data; } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/render/WorldRendererMixin.java ================================================ package graphics.kiln.blaze4d.mixin.render; import com.mojang.authlib.minecraft.client.MinecraftClient; import com.mojang.blaze3d.shaders.Uniform; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexBuffer; import com.mojang.math.Matrix4f; import graphics.kiln.blaze4d.Blaze4D; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectListIterator; import net.minecraft.client.Camera; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.LevelRenderer; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.ShaderInstance; import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher; import net.minecraft.core.BlockPos; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(LevelRenderer.class) public class WorldRendererMixin { // @Shadow private double xTransparentOld; // // @Shadow private double yTransparentOld; // // @Shadow private double zTransparentOld; @Inject(method="renderSky", at = @At("HEAD")) private void stopSky0(PoseStack poseStack, Matrix4f matrix4f, float f, Camera camera, boolean bl, Runnable runnable, CallbackInfo ci) { } @Inject(method="renderSky", at = @At("RETURN")) private void stopSky1(PoseStack poseStack, Matrix4f matrix4f, float f, Camera camera, boolean bl, Runnable runnable, CallbackInfo ci) { } /* *//** * This is one of three places where it actually renders a shader. * This one is different from the others because most of its setup process is still needed. * @author burger *//* @Overwrite public void renderChunkLayer(RenderType renderType, PoseStack modelViewStack, double xTransparent, double yTransparent, double zTransparent, Matrix4f projectionMatrix) { RenderSystem.assertOnRenderThread(); renderType.setupRenderState(); if (renderType == RenderType.translucent()) { Minecraft.getInstance().getProfiler().push("translucent_sort"); double g = xTransparent - this.xTransparentOld; double h = yTransparent - this.yTransparentOld; double i = zTransparent - this.zTransparentOld; if (g * g + h * h + i * i > 1.0) { this.xTransparentOld = xTransparent; this.yTransparentOld = yTransparent; this.zTransparentOld = zTransparent; int j = 0; for(LevelRenderer.RenderChunkInfo renderChunkInfo : this.renderChunks) { if (j < 15 && renderChunkInfo.chunk.resortTransparency(renderType, this.chunkRenderDispatcher)) { ++j; } } } Minecraft.getInstance().getProfiler().pop(); } Minecraft.getInstance().getProfiler().push("filterempty"); Minecraft.getInstance().getProfiler().popPush(() -> "render_" + renderType); boolean bl = renderType != RenderType.translucent(); ObjectListIterator objectListIterator = this.renderChunks.listIterator(bl ? 0 : this.renderChunks.size()); ShaderInstance shader = RenderSystem.getShader(); GlobalRenderSystem.updateUniforms(shader, modelViewStack.last().pose(), projectionMatrix); Uniform chunkOffset = shader.CHUNK_OFFSET; while(true) { if (bl) { if (!objectListIterator.hasNext()) { break; } } else if (!objectListIterator.hasPrevious()) { break; } LevelRenderer.RenderChunkInfo renderChunkInfo2 = bl ? objectListIterator.next() : objectListIterator.previous(); ChunkRenderDispatcher.RenderChunk renderChunk = renderChunkInfo2.chunk; if (!renderChunk.getCompiledChunk().isEmpty(renderType)) { VertexBuffer vertexBuffer = renderChunk.getBuffer(renderType); BlockPos blockPos = renderChunk.getOrigin(); if (chunkOffset != null) { chunkOffset.set((float)((double)blockPos.getX() - xTransparent), (float)((double)blockPos.getY() - yTransparent), (float)((double)blockPos.getZ() - zTransparent)); } vertexBuffer.drawChunkLayer(); } } shader.clear(); Minecraft.getInstance().getProfiler().pop(); renderType.clearRenderState(); }*/ } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/shader/GlStateManagerMixin.java ================================================ package graphics.kiln.blaze4d.mixin.shader; import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.systems.RenderSystem; import org.lwjgl.opengl.GL20; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.charset.StandardCharsets; import java.util.List; /** * This Mixin handles the interactions between Minecraft shaders and GL programs and passes it onto rosella */ @Mixin(value = GlStateManager.class, remap = false) public class GlStateManagerMixin { // // //======================== // // SHADERS // //======================== // // /** // * @author Blaze4D // * @reason To Integrate Shaders // */ // @Overwrite // public static int glCreateShader(int type) { // RenderSystem.assertOnRenderThread(); // // Check last shader's type to see if they belong in the same shader // // TODO: maybe support more shader types in the future? // ShaderType rosellaType = type == GL20.GL_VERTEX_SHADER ? ShaderType.VERTEX_SHADER : ShaderType.FRAGMENT_SHADER; // ShaderContext shaderContext = new ShaderContext(); // shaderContext.glShaderType = type; // shaderContext.rosellaShaderType = rosellaType; // GlobalRenderSystem.SHADER_MAP.put(GlobalRenderSystem.nextShaderId, shaderContext); // GlobalRenderSystem.nextShaderId++; // return GlobalRenderSystem.nextShaderId - 1; // } // // /** // * @author Blaze4D // * @reason To Integrate Shaders // */ // @Overwrite // public static void glShaderSource(int shader, List shaderLines) { // RenderSystem.assertOnRenderThread(); // ShaderContext context = GlobalRenderSystem.SHADER_MAP.get(shader); // if (context == null) { // throw new RuntimeException("Failed to get ShaderContext. (No shader was found with id " + shader + ")"); // } // // context.shader = shaderSrcToResource(shaderLines); // } // // /** // * @author Blaze4D // * @reason To Integrate Shaders // * If something ever goes wrong, assume its our fault :( // */ // @Overwrite // public static String glGetShaderInfoLog(int shader, int maxLength) { // RenderSystem.assertOnRenderThread(); // return "Internal Blaze4D Error"; // } // // /** // * @author Blaze4D // * @reason To Integrate Shaders // *

// * This method is really just a method to get the compilation status of a shader. // * as long as no exceptions have been thrown, assume everything is OK // */ // @Overwrite // public static int glGetShaderi(int shader, int pname) { // RenderSystem.assertOnRenderThread(); // return GL20.GL_TRUE; // } // // //======================== // // SHADER PROGRAMS // //======================== // // /** // * @author Blaze4D // * @reason To Integrate Shader Programs // */ // @Overwrite // public static int glCreateProgram() { // RenderSystem.assertOnRenderThread(); // MinecraftShaderProgram program = new MinecraftShaderProgram( // null, // null, // Blaze4D.rosella.common.device, // Blaze4D.rosella.common.memory, // GlobalRenderSystem.DEFAULT_MAX_OBJECTS, // GlobalRenderSystem.blaze4d$capturedShaderProgram.blaze4d$getUniforms(), // GlobalRenderSystem.processedSamplers); // GlobalRenderSystem.processedSamplers.clear(); // GlobalRenderSystem.currentSamplerBinding = 1; // GlobalRenderSystem.SHADER_PROGRAM_MAP.put(GlobalRenderSystem.nextShaderProgramId, program); // Blaze4D.rosella.renderer.rebuildCommandBuffers(Blaze4D.rosella.renderer.mainRenderPass); // return GlobalRenderSystem.nextShaderProgramId++; // } // // /** // * @author Blaze4D // * @reason To Integrate Shader Programs // */ // @Overwrite // public static void glAttachShader(int programId, int shaderId) { // RenderSystem.assertOnRenderThread(); // ShaderContext shader = GlobalRenderSystem.SHADER_MAP.get(shaderId); // RawShaderProgram program = GlobalRenderSystem.SHADER_PROGRAM_MAP.get(programId); // if (program == null) { // throw new RuntimeException("Shader was requested without begin registered"); // } // // if (shader.rosellaShaderType == ShaderType.VERTEX_SHADER) { // program.setVertexShader(shader.shader); // } else { // program.setFragmentShader(shader.shader); // } // } // // /** // * @author Blaze4D // * @reason To Integrate Shader Programs // *

// * Basically compiles the shader program // */ // @Overwrite // public static void glLinkProgram(int program) { // RenderSystem.assertOnRenderThread(); //// Identifier id = GlobalRenderSystem.generateId(program); // Blaze4D.rosella.baseObjectManager.addShader(GlobalRenderSystem.SHADER_PROGRAM_MAP.get(program)); // } // // /** // * @author Blaze4D // * @reason To Integrate Shader Programs // *

// * Since shaders should define this in the vertex format, we shouldn't need to worry about this. // */ // @Overwrite // public static void _glBindAttribLocation(int program, int index, CharSequence name) { // RenderSystem.assertOnRenderThread(); // } // // /** // * @author Blaze4D // * @reason To Integrate Shader Programs // *

// * C Documentation: "Returns a parameter from a program object" // * It really just's lets you query things from the program like status, etc // */ // @Overwrite // public static int glGetProgrami(int program, int pname) { // RenderSystem.assertOnRenderThread(); // switch (pname) { // case GL20.GL_LINK_STATUS, GL20.GL_COMPILE_STATUS -> { // // Since we throw exceptions instead of failing quietly, assume everything is OK // return 1; // } // // default -> GlobalRenderSystem.programErrorLog = "glGetProgramI is not implemented for " + pname; // } // return 0; // } // // /** // * @author Blaze4D // * @reason To Integrate Shader Programs // *

// * When something errors, this is called to figure out what went wrong. // */ // @Overwrite // public static String glGetProgramInfoLog(int program, int maxLength) { // RenderSystem.assertOnRenderThread(); // String lastError = GlobalRenderSystem.programErrorLog; // GlobalRenderSystem.programErrorLog = ""; // return lastError; // } // // //======================== // // UTILITIES // //======================== // // /** // * Converts a list of lines of shader source code into a {@link Resource} which can be loaded by Rosella // * // * @param shaderSrc the source of the shader // * @return a readable resource for {@link graphics.kiln.rosella.Rosella} // */ // private static Resource shaderSrcToResource(List shaderSrc) { // byte[] shaderBytes = String.join("\n", shaderSrc).getBytes(StandardCharsets.UTF_8); // return new ByteArrayResource(shaderBytes); // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void glCompileShader(int shader) { // } // // /** // * @author Blaze4D // */ // @Overwrite // public static int _glGetUniformLocation(int program, CharSequence name) { // return 0; // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void glDeleteShader(int shader) { // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void glDeleteProgram(int program) { // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void _glUniform1(int location, IntBuffer value) { // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void _glUniform1i(int location, int value) { // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void _glUniform1(int location, FloatBuffer value) { // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void _glUniform2(int location, IntBuffer value) { // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void _glUniform2(int location, FloatBuffer value) { // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void _glUniform3(int location, IntBuffer value) { // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void _glUniform3(int location, FloatBuffer value) { // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void _glUniform4(int location, IntBuffer value) { // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void _glUniform4(int location, FloatBuffer value) { // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void _glUniformMatrix2(int location, boolean transpose, FloatBuffer value) { // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void _glUniformMatrix3(int location, boolean transpose, FloatBuffer value) { // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void _glUniformMatrix4(int location, boolean transpose, FloatBuffer value) { // } // // /** // * @author Blaze4D // */ // @Overwrite // public static int _glGetAttribLocation(int program, CharSequence name) { // return 0; // } // // /** // * @author Blaze4D // */ // @Overwrite // public static void _glUseProgram(int program) { // } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/shader/GlUniformMixin.java ================================================ package graphics.kiln.blaze4d.mixin.shader; import com.mojang.blaze3d.shaders.AbstractUniform; import com.mojang.blaze3d.shaders.Shader; import com.mojang.blaze3d.shaders.Uniform; import com.mojang.math.Matrix4f; import com.mojang.math.Vector3f; import graphics.kiln.blaze4d.Blaze4D; import graphics.kiln.blaze4d.api.B4DShader; import graphics.kiln.blaze4d.core.Blaze4DCore; import graphics.kiln.blaze4d.core.types.B4DUniform; import graphics.kiln.blaze4d.core.types.B4DUniformData; import org.checkerframework.checker.units.qual.A; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import javax.annotation.Nullable; @Mixin(Uniform.class) public abstract class GlUniformMixin extends AbstractUniform implements graphics.kiln.blaze4d.api.B4DUniform { @Final @Shadow private String name; @Final @Shadow private int type; @Final @Shadow private Shader parent; @Final @Nullable private B4DUniform b4dUniform; @Final @Nullable private B4DUniformData b4DUniformData; @Inject(method = "", at = @At("TAIL")) private void init(String string, int i, int j, Shader shader, CallbackInfo ci) { B4DUniform b4dUniform = null; switch (this.name) { case "ModelViewMat" -> { if (this.type == Uniform.UT_MAT4) { b4dUniform = B4DUniform.MODEL_VIEW_MATRIX; } else { Blaze4D.LOGGER.warn("Uniform ModelViewMat had type not equal to UT_MAT4. ignoring!"); } } case "ProjMat" -> { if (this.type == Uniform.UT_MAT4) { b4dUniform = B4DUniform.PROJECTION_MATRIX; } else { Blaze4D.LOGGER.warn("Uniform ProjMat had type not equal to UT_MAT4. ignoring!"); } } case "IViewRotMat" -> { if (this.type == Uniform.UT_MAT4) { b4dUniform = B4DUniform.INVERSE_VIEW_ROTATION_MATRIX; } else { Blaze4D.LOGGER.warn("Uniform IViewRotMat had type not equal to UT_MAT4. ignoring!"); } } case "TextureMat" -> { if (this.type == Uniform.UT_MAT4) { b4dUniform = B4DUniform.TEXTURE_MATRIX; } else { Blaze4D.LOGGER.warn("Uniform TextureMat had type not equal to UT_MAT4. ignoring!"); } } case "ScreenSize" -> { if (this.type == Uniform.UT_FLOAT2) { b4dUniform = B4DUniform.SCREEN_SIZE; } else { Blaze4D.LOGGER.warn("Uniform ScreenSize had type not equal to UT_FLOAT2. ignoring!"); } } case "ColorModulator" -> { if (this.type == Uniform.UT_FLOAT4) { b4dUniform = B4DUniform.COLOR_MODULATOR; } else { Blaze4D.LOGGER.warn("Uniform ColorModulator had type not equal to UT_FLOAT4. ignoring!"); } } case "Light0_Direction" -> { if (this.type == Uniform.UT_FLOAT3) { b4dUniform = B4DUniform.LIGHT0_DIRECTION; } else { Blaze4D.LOGGER.warn("Uniform Light0_Direction had type not equal to UT_FLOAT3. ignoring!"); } } case "Light1_Direction" -> { if (this.type == Uniform.UT_FLOAT3) { b4dUniform = B4DUniform.LIGHT1_DIRECTION; } else { Blaze4D.LOGGER.warn("Uniform Light1_Direction had type not equal to UT_FLOAT3. ignoring!"); } } case "FogStart" -> { if (this.type == Uniform.UT_FLOAT1) { b4dUniform = B4DUniform.FOG_START; } else { Blaze4D.LOGGER.warn("Uniform FogStart had type not equal to UT_FLOAT1. ignoring!"); } } case "FogEnd" -> { if (this.type == Uniform.UT_FLOAT1) { b4dUniform = B4DUniform.FOG_END; } else { Blaze4D.LOGGER.warn("Uniform FogEnd had type not equal to UT_FLOAT1. ignoring!"); } } case "FogColor" -> { if (this.type == Uniform.UT_FLOAT4) { b4dUniform = B4DUniform.FOG_COLOR; } else { Blaze4D.LOGGER.warn("Uniform FogColor had type not equal to UT_FLOAT4. ignoring!"); } } case "FogShape" -> { if (this.type == Uniform.UT_INT1) { b4dUniform = B4DUniform.FOG_SHAPE; } else { Blaze4D.LOGGER.warn("Uniform FogShape had type not equal to UT_INT1. ignoring!"); } } case "LineWidth" -> { if (this.type == Uniform.UT_FLOAT1) { b4dUniform = B4DUniform.LINE_WIDTH; } else { Blaze4D.LOGGER.warn("Uniform LineWidth had type not equal to UT_FLOAT1. ignoring!"); } } case "GameTime" -> { if (this.type == Uniform.UT_FLOAT1) { b4dUniform = B4DUniform.GAME_TIME; } else { Blaze4D.LOGGER.warn("Uniform GameTime had type not equal to UT_FLOAT1. ignoring!"); } } case "ChunkOffset" -> { if (this.type == Uniform.UT_FLOAT3) { b4dUniform = B4DUniform.CHUNK_OFFSET; } else { Blaze4D.LOGGER.warn("Uniform ChunkOffset had type not equal to UT_FLOAT3. ignoring!"); } } } this.b4dUniform = b4dUniform; if (this.b4dUniform != null) { this.b4DUniformData = new B4DUniformData(); } else { this.b4DUniformData = null; } } public B4DUniform getB4DUniform() { return this.b4dUniform; } @Inject(method = "set(F)V", at = @At("HEAD")) private void setVec1f(float x, CallbackInfo ci) { } @Inject(method = "set(FFF)V", at = @At("HEAD")) private void setVec3f(float x, float y, float z, CallbackInfo ci) { if(this.b4dUniform != null) { assert this.b4DUniformData != null; long shaderId = ((B4DShader) this.parent).b4dGetShaderId(); if (this.b4dUniform == B4DUniform.CHUNK_OFFSET) { this.b4DUniformData.setChunkOffset(x, y, z); Blaze4D.pushUniform(shaderId, this.b4DUniformData); } } } @Inject(method = "set(Lcom/mojang/math/Vector3f;)V", at = @At("HEAD")) private void setVec3f(Vector3f vec, CallbackInfo ci) { if(this.b4dUniform != null) { assert this.b4DUniformData != null; long shaderId = ((B4DShader) this.parent).b4dGetShaderId(); if (this.b4dUniform == B4DUniform.CHUNK_OFFSET) { this.b4DUniformData.setChunkOffset(vec.x(), vec.y(), vec.z()); Blaze4D.pushUniform(shaderId, this.b4DUniformData); } } } @Inject(method = "set(Lcom/mojang/math/Matrix4f;)V", at = @At("HEAD")) private void setMat4f32(Matrix4f mat, CallbackInfo ci) { if(this.b4dUniform != null) { assert this.b4DUniformData != null; long shaderId = ((B4DShader) this.parent).b4dGetShaderId(); if (this.b4dUniform == B4DUniform.MODEL_VIEW_MATRIX) { this.b4DUniformData.setModelViewMatrix(mat.m00, mat.m01, mat.m02, mat.m03, mat.m10, mat.m11, mat.m12, mat.m13, mat.m20, mat.m21, mat.m22, mat.m23, mat.m30, mat.m31, mat.m32, mat.m33); Blaze4D.pushUniform(shaderId, this.b4DUniformData); } else if (this.b4dUniform == B4DUniform.PROJECTION_MATRIX) { this.b4DUniformData.setProjectionMatrix(mat.m00, mat.m01, mat.m02, mat.m03, mat.m10, mat.m11, mat.m12, mat.m13, mat.m20, mat.m21, mat.m22, mat.m23, mat.m30, mat.m31, mat.m32, mat.m33); Blaze4D.pushUniform(shaderId, this.b4DUniformData); } } } // @Unique // private long writeLocation; // // // @Final // @Shadow // private IntBuffer intValues; // // @Final // @Shadow // private FloatBuffer floatValues; // // @Final // @Shadow // private String name; // // @Shadow // private boolean dirty; // // @Shadow // protected abstract void markDirty(); // // @Override // public void writeLocation(long address) { // writeLocation = address; // markDirty(); // } // // @Inject(method = "upload", at = @At("HEAD"), cancellable = true) // public void uploadToRosellaBuffer(CallbackInfo ci) { // if (writeLocation == MemoryUtil.NULL || !dirty) { // return; // } // // this.dirty = false; // if (this.type <= 3) { // MemoryUtil.memCopy(MemoryUtil.memAddress(intValues), writeLocation, (long) (type + 1) * Integer.BYTES); // } else if (this.type <= 7) { // MemoryUtil.memCopy(MemoryUtil.memAddress(floatValues), writeLocation, (long) (type - 3) * Float.BYTES); // } else if (this.type <= 10) { // MemoryUtil.memCopy(MemoryUtil.memAddress(floatValues), writeLocation, (long) Mth.square(type - 6) * Float.BYTES); // } else { // throw new UnsupportedOperationException("Uniform has unexpected type " + type); // } // ci.cancel(); // } // // @Override // public void set(Matrix4f matrix4f) { // org.joml.Matrix4f matrix; // if (this.name.equals("ProjMat")) { // matrix = ConversionUtils.mcToJomlProjectionMatrix(matrix4f); // } else { // matrix = ConversionUtils.mcToJomlMatrix(matrix4f); // } // matrix.get(0, floatValues); // markDirty(); // } // // @Override // public int alignOffset(int currentOffset) { // return switch (type) { // case 1, 5 -> Mth.roundToward(currentOffset, 8); // case 2, 3, 6, 7, 8, 9, 10 -> Mth.roundToward(currentOffset, 16); // default -> currentOffset; // }; // } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/shader/RenderSystemMixin.java ================================================ package graphics.kiln.blaze4d.mixin.shader; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.renderer.ShaderInstance; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; import java.util.function.Supplier; @Mixin(value = RenderSystem.class, remap = false) public abstract class RenderSystemMixin { // @Shadow // @Nullable // private static ShaderInstance shader; // // /** // * @author Blaze4D // * @reason To Integrate Shader Programs // */ // @Overwrite // public static void setShader(Supplier supplier) { // ShaderInstance result = supplier.get(); // if (result == null) { // return; // } // RenderSystemMixin.shader = result; // RawShaderProgram rawProgram = GlobalRenderSystem.SHADER_PROGRAM_MAP.get(RenderSystemMixin.shader.getId()); // GlobalRenderSystem.activeShader = Blaze4D.rosella.common.shaderManager.getOrCreateShader(rawProgram); // } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/shader/ShaderAccessor.java ================================================ package graphics.kiln.blaze4d.mixin.shader; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; import com.mojang.blaze3d.shaders.Uniform; import java.util.List; import net.minecraft.client.renderer.ShaderInstance; @Mixin(ShaderInstance.class) public interface ShaderAccessor { // // @Accessor(value = "uniforms") // List blaze4d$getUniforms(); } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/shader/ShaderMixin.java ================================================ package graphics.kiln.blaze4d.mixin.shader; import com.mojang.blaze3d.shaders.Uniform; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.VertexFormat; import com.mojang.math.Matrix4f; import graphics.kiln.blaze4d.Blaze4D; import graphics.kiln.blaze4d.api.B4DShader; import graphics.kiln.blaze4d.api.Utils; import graphics.kiln.blaze4d.core.types.B4DUniform; import graphics.kiln.blaze4d.core.types.B4DVertexFormat; import net.minecraft.client.renderer.ShaderInstance; import net.minecraft.server.packs.resources.ResourceProvider; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.List; @Mixin(ShaderInstance.class) public class ShaderMixin implements B4DShader { @Final @Shadow private List uniforms; @Final private long b4dShaderId = 0L; @Inject(method = "", at = @At(value = "TAIL")) private void initShader(ResourceProvider resourceProvider, String string, VertexFormat vertexFormat, CallbackInfo ci) { try (B4DVertexFormat nativeFormat = new B4DVertexFormat()) { long usedUniforms = 0L; for (Uniform uniform : this.uniforms) { B4DUniform b4DUniform = ((graphics.kiln.blaze4d.api.B4DUniform) uniform).getB4DUniform(); if (b4DUniform != null) { usedUniforms |= b4DUniform.getValue(); } } if (Utils.convertVertexFormat(vertexFormat, nativeFormat)) { this.b4dShaderId = Blaze4D.core.createShader(nativeFormat, usedUniforms); } else { Blaze4D.LOGGER.warn("Shader vertex format did not contain position. Skipping!"); } } catch (Exception e) { throw new RuntimeException("Failed to create shader", e); } } @Inject(method = "apply", at = @At(value = "TAIL")) private void applyShader(CallbackInfo ci) { Matrix4f proj = RenderSystem.getProjectionMatrix(); Matrix4f modelView = RenderSystem.getModelViewMatrix(); } @Inject(method = "close", at = @At(value = "TAIL")) private void destroyShader(CallbackInfo ci) { Blaze4D.core.destroyShader(this.b4dShaderId); } @Override public long b4dGetShaderId() { return this.b4dShaderId; } // // @ModifyArg(method = "getOrCreate", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/shaders/Program;compileShader(Lcom/mojang/blaze3d/shaders/Program$Type;Ljava/lang/String;Ljava/io/InputStream;Ljava/lang/String;Lcom/mojang/blaze3d/preprocessor/GlslPreprocessor;)Lcom/mojang/blaze3d/shaders/Program;"), index = 2) // private static InputStream no(Program.Type type, String name, InputStream stream, String domain, GlslPreprocessor loader) throws IOException { // String originalSource = new String(stream.readAllBytes()); // Rosella.LOGGER.debug("Processing shader " + name + type.getExtension()); // Map uniforms = new LinkedHashMap<>(); // // for (Uniform uniform : GlobalRenderSystem.blaze4d$capturedShaderProgram.blaze4d$getUniforms()) { // if (uniforms.put(uniform.getName(), uniform.getType()) != null) { // throw new IllegalStateException("Duplicate key"); // } // } // // VanillaShaderProcessor.ConversionData conversionData = VanillaShaderProcessor.process( // List.of(originalSource), // uniforms, // GlobalRenderSystem.processedSamplers, // GlobalRenderSystem.currentSamplerBinding // ); // // GlobalRenderSystem.currentSamplerBinding = conversionData.samplerBinding(); // String transformedToVulkan = String.join("\n", conversionData.lines()); // return new ByteArrayInputStream(transformedToVulkan.getBytes()); // } // // @Inject(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ShaderInstance;parseBlendNode(Lcom/google/gson/JsonObject;)Lcom/mojang/blaze3d/shaders/BlendMode;")) // public void captureShaderForStaticMethods(ResourceProvider factory, String name, VertexFormat format, CallbackInfo ci) { // GlobalRenderSystem.blaze4d$capturedShaderProgram = (ShaderAccessor) this; // } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/texture/LightmapTextureManagerMixin.java ================================================ package graphics.kiln.blaze4d.mixin.texture; import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.LightTexture; import net.minecraft.resources.ResourceLocation; import org.lwjgl.opengl.GL13; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(LightTexture.class) public class LightmapTextureManagerMixin { // // @Shadow @Final private ResourceLocation lightTextureLocation; // // @Shadow @Final private Minecraft minecraft; // // @Inject(method = "turnOnLightLayer", at = @At("HEAD"), cancellable = true) // private void removeBindTexture(CallbackInfo ci) { // RenderSystem.setShaderTexture(2, this.lightTextureLocation); // int prevActiveTexture = GlStateManager._getActiveTexture(); // RenderSystem.activeTexture(GL13.GL_TEXTURE2); // this.minecraft.getTextureManager().bindForSetup(this.lightTextureLocation); // RenderSystem.activeTexture(prevActiveTexture); // RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); // ci.cancel(); // } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/texture/NativeImageMixin.java ================================================ package graphics.kiln.blaze4d.mixin.texture; import com.mojang.blaze3d.platform.NativeImage; import org.jetbrains.annotations.NotNull; import org.lwjgl.system.MemoryUtil; import org.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.nio.ByteBuffer; @Mixin(NativeImage.class) public abstract class NativeImageMixin /*implements UploadableImage*/ { // @Shadow // @Final // private int width; // // @Shadow // @Final // private int height; // // @Shadow // @Final // private NativeImage.Format format; // // @Shadow // private long pixels; // // @Shadow // @Final // private long size; // // @Shadow // public abstract void close(); // // @Unique // private ImageFormat rosellaFormat; // // @Inject(method = "_upload", at = @At("HEAD"), cancellable = true) // private void uploadToRosella(int level, int offsetX, int offsetY, int unpackSkipPixels, int unpackSkipRows, int width, int height, boolean blur, boolean clamp, boolean mipmap, boolean close, CallbackInfo ci) { // TextureManager textureManager = Blaze4D.rosella.common.textureManager; // textureManager.setTextureSampler( // GlobalRenderSystem.boundTextureIds[GlobalRenderSystem.activeTextureSlot], // GlobalRenderSystem.getSamplerNameForSlot(GlobalRenderSystem.activeTextureSlot), // new SamplerCreateInfo(blur ? TextureFilter.LINEAR : TextureFilter.NEAREST, clamp ? WrapMode.CLAMP_TO_EDGE : WrapMode.REPEAT) // ); // textureManager.drawToExistingTexture( // Blaze4D.rosella.renderer, // GlobalRenderSystem.boundTextureIds[GlobalRenderSystem.activeTextureSlot], // this, // new ImageRegion(width, height, unpackSkipPixels, unpackSkipRows), // new ImageRegion(width, height, offsetX, offsetY) // ); // if (close) { // this.close(); // } // ci.cancel(); // } // // @Unique // @Override // public int getWidth() { // return width; // } // // @Unique // @Override // public int getHeight() { // return height; // } // // @Unique // @NotNull // @Override // public ImageFormat getFormat() { // if (rosellaFormat == null) { // rosellaFormat = ConversionUtils.glToRosellaImageFormat(format.glFormat()); // getPixelDataFormat returns the gl format // } // // return rosellaFormat; // } // // @Unique // @Override // public int getSize() { // return (int) size; // } // // @Unique // @Override // public ByteBuffer getPixels() { // return MemoryUtil.memByteBuffer(pixels, getSize()); // } // // /** // * @author ramidzkh // * @reason Don't die screenshotting // */ // @Overwrite // public void downloadTexture(int lod, boolean captureAlpha) { // } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/texture/RenderSystemMixin.java ================================================ package graphics.kiln.blaze4d.mixin.texture; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.texture.AbstractTexture; import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.resources.ResourceLocation; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(value = RenderSystem.class, remap = false) public class RenderSystemMixin { // @Inject(method = "setShaderTexture(ILnet/minecraft/resources/ResourceLocation;)V", at = @At("HEAD"), require = 0, cancellable = true) // private static void setTextureFromIdentifier(int slot, ResourceLocation identifier, CallbackInfo ci) { // setTexture(slot, identifier, ci); // } // // @Inject(method = "setShaderTexture(ILnet/minecraft/class_2960;)V", at = @At("HEAD"), require = 0, cancellable = true) // ugly hack to get around mixin not remapping properly // private static void setTextureFromIdentifierInIntermediary(int slot, ResourceLocation identifier, CallbackInfo ci) { // setTexture(slot, identifier, ci); // } // // private static void setTexture(int slot, ResourceLocation identifier, CallbackInfo ci) { // if (slot >= 0 && slot < GlobalRenderSystem.MAX_TEXTURES) { // TextureManager textureManager = Minecraft.getInstance().getTextureManager(); // AbstractTexture abstractTexture = textureManager.getTexture(identifier); // GlobalRenderSystem.boundTextureIds[slot] = abstractTexture.getId(); // } // ci.cancel(); // } // // @Inject(method = "setShaderTexture(II)V", at = @At("HEAD"), cancellable = true) // private static void setTextureFromId(int slot, int texId, CallbackInfo ci) { // if (slot >= 0 && slot < GlobalRenderSystem.MAX_TEXTURES) { // GlobalRenderSystem.boundTextureIds[slot] = texId; // } // ci.cancel(); // } // // @Inject(method = "getShaderTexture", at = @At("HEAD"), cancellable = true) // private static void getTextureFromUs(int slot, CallbackInfoReturnable cir) { // cir.setReturnValue(slot >= 0 && slot < GlobalRenderSystem.MAX_TEXTURES ? GlobalRenderSystem.boundTextureIds[slot] : 0); // } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/texture/TextureManagerMixin.java ================================================ package graphics.kiln.blaze4d.mixin.texture; import net.minecraft.client.renderer.texture.TextureManager; import org.spongepowered.asm.mixin.Mixin; @Mixin(TextureManager.class) public class TextureManagerMixin { // @Inject(method = "lambda$reload$4", at = @At("RETURN"), remap = false) // private void reloadTextures(CallbackInfo ci) { // Blaze4D.rosella.objectManager.submitMaterials(); // } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/texture/TextureUtilMixin_Development.java ================================================ package graphics.kiln.blaze4d.mixin.texture; import com.mojang.blaze3d.platform.NativeImage; import com.mojang.blaze3d.platform.TextureUtil; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(value = TextureUtil.class, remap = false) public class TextureUtilMixin_Development { // @Inject(method = "prepareImage(Lcom/mojang/blaze3d/platform/NativeImage$InternalGlFormat;IIII)V", at = @At("HEAD"), cancellable = true) // private static void createRosellaTexture(NativeImage.InternalGlFormat internalFormat, int id, int maxLevel, int width, int height, CallbackInfo ci) { // TextureUtils.createRosellaTexture(internalFormat, id, maxLevel, width, height, ci); // } } ================================================ FILE: mod/src/main/java/graphics/kiln/blaze4d/mixin/texture/TextureUtilMixin_Runtime.java ================================================ package graphics.kiln.blaze4d.mixin.texture; import com.mojang.blaze3d.platform.NativeImage; import com.mojang.blaze3d.platform.TextureUtil; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(value = TextureUtil.class, remap = false) public class TextureUtilMixin_Runtime { // // @Inject(method = "prepareImage(Lnet/minecraft/class_1011$class_1013;IIII)V", at = @At("HEAD"), cancellable = true) // private static void createRosellaTexture(NativeImage.InternalGlFormat internalFormat, int id, int maxLevel, int width, int height, CallbackInfo ci) { // TextureUtils.createRosellaTexture(internalFormat, id, maxLevel, width, height, ci); // } } ================================================ FILE: mod/src/main/resources/blaze4d.aw ================================================ accessWidener v1 named # Make animated textures stop animating accessible class net/minecraft/client/renderer/texture/TextureAtlasSprite$AnimatedTexture # Make Minecraft Shut Up accessible class net/minecraft/client/renderer/GpuWarnlistManager$Preparations # Access to Matrix4f so i can convert Minecraft matrices to JOML matrices accessible field com/mojang/math/Matrix4f m00 F accessible field com/mojang/math/Matrix4f m01 F accessible field com/mojang/math/Matrix4f m02 F accessible field com/mojang/math/Matrix4f m03 F accessible field com/mojang/math/Matrix4f m10 F accessible field com/mojang/math/Matrix4f m11 F accessible field com/mojang/math/Matrix4f m12 F accessible field com/mojang/math/Matrix4f m13 F accessible field com/mojang/math/Matrix4f m20 F accessible field com/mojang/math/Matrix4f m21 F accessible field com/mojang/math/Matrix4f m22 F accessible field com/mojang/math/Matrix4f m23 F accessible field com/mojang/math/Matrix4f m30 F accessible field com/mojang/math/Matrix4f m31 F accessible field com/mojang/math/Matrix4f m32 F accessible field com/mojang/math/Matrix4f m33 F # Access to ChunkInfo for world rendering accessible class net/minecraft/client/renderer/LevelRenderer$RenderChunkInfo accessible field net/minecraft/client/renderer/LevelRenderer$RenderChunkInfo chunk Lnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk; ================================================ FILE: mod/src/main/resources/blaze4d.mixins.json ================================================ { "required": true, "minVersion": "0.8", "package": "graphics.kiln.blaze4d.mixin", "compatibilityLevel": "JAVA_18", "plugin": "graphics.kiln.blaze4d.Blaze4DMixinPlugin", "mixins": [ ], "client": [ "render.VertexBufferMixin", "render.BufferUploaderMixin", "render.WorldRendererMixin", "render.RenderSystemMixin", "shader.GlStateManagerMixin", "shader.RenderSystemMixin", "shader.ShaderAccessor", "shader.ShaderMixin", "shader.GlUniformMixin", "texture.LightmapTextureManagerMixin", "texture.NativeImageMixin", "texture.RenderSystemMixin", "texture.TextureManagerMixin", "texture.TextureUtilMixin_Development", "texture.TextureUtilMixin_Runtime", "integration.GlStateManagerMixin", "integration.FramebufferMixin", "integration.GlDebugMixin", "integration.GLXMixin", "integration.MinecraftClientMixin", "integration.VideoWarningManagerMixin", "integration.WindowFramebufferMixin", "integration.WindowMixin" ], "injectors": { "defaultRequire": 1 } } ================================================ FILE: mod/src/main/resources/fabric.mod.json ================================================ { "schemaVersion": 1, "id": "blaze4d", "version": "${version}", "name": "Blaze 4D", "description": "Blaze3D But Vulkan & mod compatibility", "authors": [ "hydos", "OroArmor", "burgerdude", "ramidzkh" ], "contributors": [ "CodingRays", "Sir Obsidian", "imperatorstorm" ], "license": "LGPL-3.0-or-later", "icon": "assets/blaze4d/icon.png", "environment": "*", "entrypoints": { "client": [ "graphics.kiln.blaze4d.Blaze4D" ], "preLaunch": [ "graphics.kiln.blaze4d.Blaze4DPreLaunch" ] }, "mixins": [ "blaze4d.mixins.json" ], "accessWidener": "blaze4d.aw", "depends": { "fabricloader": ">=0.13.3", "minecraft": "1.19.x" }, "breaks": { "canvas": "*", "optifabric": "*", "sodium": "*", "iris": "*" } } ================================================ FILE: settings.gradle.kts ================================================ rootProject.name = "blaze4d" pluginManagement { repositories { gradlePluginPortal() maven { name = "FabricMC" url = uri("https://maven.fabricmc.net/") } maven { name = "Cotton" url = uri("https://server.bbkr.space/artifactory/libs-release/") } } } include("mod", "core:api", "core:natives", "core:assets")