Files
OpenEmu-Shaders/Source/ShaderPassCompiler+Reflection.swift

690 lines
30 KiB
Swift

// Copyright (c) 2022, OpenEmu Team
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the OpenEmu Team nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY OpenEmu Team ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL OpenEmu Team BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import CSPIRVCross
import Foundation
import os.log
extension ShaderPassCompiler {
func makeSymbols() -> ShaderSymbols? {
let sym = ShaderSymbols()
// add aliases
for pass in shader.passes {
guard let name = pass.alias, !name.isEmpty else { continue }
let index = pass.index
guard sym.addTextureSemantic(.passOutput, atIndex: index, name: name) else { return nil }
guard sym.addTextureBufferSemantic(.passOutputSize, atIndex: index, name: "\(name)Size") else { return nil }
guard sym.addTextureSemantic(.passFeedback, atIndex: index, name: "\(name)Feedback") else { return nil }
guard sym.addTextureBufferSemantic(.passFeedbackSize, atIndex: index, name: "\(name)FeedbackSize") else { return nil }
}
for (i, lut) in shader.luts.enumerated() {
guard sym.addTextureSemantic(.user, atIndex: i, name: lut.name) else { return nil }
guard sym.addTextureBufferSemantic(.userSize, atIndex: i, name: "\(lut.name)Size") else { return nil }
}
for (i, param) in shader.parameters.enumerated() {
guard sym.addFloatParameterSemantic(atIndex: i, name: param.name) else { return nil }
}
return sym
}
// swiftlint:disable cyclomatic_complexity
func reflect(passNumber: Int, withSymbols sym: ShaderSymbols, withVertexCompiler vsCompiler: SPVCompiler, fragmentCompiler fsCompiler: SPVCompiler) -> ShaderPassReflection? {
let ref = ShaderPassReflection(passNumber: passNumber)
var vsResources: SPVResources?
vsCompiler.create_shader_resources(&vsResources)
guard let vsResources else {
return nil
}
var fsResources: SPVResources?
fsCompiler.create_shader_resources(&fsResources)
guard let fsResources else {
return nil
}
do {
try validateResources(vsResources: vsResources, fsResources: fsResources)
} catch {
return nil
}
// validate input to vertex shader
var list: UnsafePointer<__spvc_reflected_resource>?
var listSize = 0
vsResources.get_resource_list_for_type(type: .stageInput, list: &list, size: &listSize)
if listSize != 2 {
// os_log_error(OE_LOG_DEFAULT, "vertex shader input must have two attributes");
return nil
}
var resources = UnsafeBufferPointer<__spvc_reflected_resource>(start: list, count: listSize)
var mask: UInt = 0
mask |= 1 << vsCompiler.get_decoration(id: resources[0].id, decoration: .location)
mask |= 1 << vsCompiler.get_decoration(id: resources[1].id, decoration: .location)
if mask != 3 {
// os_log_error(OE_LOG_DEFAULT, "vertex shader input attributes must use (location = 0) and (location = 1)");
return nil
}
// validate number of render targets for fragment shader
listSize = 0
fsResources.get_resource_list_for_type(type: .stageOutput, list: &list, size: &listSize)
if listSize != 1 {
// os_log_error(OE_LOG_DEFAULT, "fragment shader must have a single output");
return nil
}
resources = UnsafeBufferPointer<__spvc_reflected_resource>(start: list, count: listSize)
if fsCompiler.get_decoration(id: resources[0].id, decoration: .location) != 0 {
// os_log_error(OE_LOG_DEFAULT, "fragment shader output must use (location = 0)");
return nil
}
// get uniform and push buffers
func getResource(_ r: SPVResources, _ t: SPVResourceType, _ err: String) throws -> __spvc_reflected_resource? {
var list: UnsafePointer<__spvc_reflected_resource>?
var listSize = 0
if r.get_resource_list_for_type(type: t, list: &list, size: &listSize).errorResult != nil {
throw ValidationError.apiError
}
guard listSize < 2
else {
// os_log_error(OE_LOG_DEFAULT, err);
throw ValidationError.unexpectedResource
}
return listSize == 1 ? list?.pointee : nil
}
// vertex UBO binding
do {
if let bufRes = try getResource(vsResources, .uniformBuffer, "vertex shader must use zero or one uniform buffer") {
if vsCompiler.get_decoration(id: bufRes.id, decoration: .descriptorSet) != 0 {
// os_log_error(OE_LOG_DEFAULT, "vertex shader resources must use descriptor set #0");
return nil
}
let binding = Int(vsCompiler.mslGetAutomaticResourceBinding(bufRes.id))
var desc = ref.ubo ?? BufferBindingDescriptor()
desc.bindingVert = binding
var sz = 0
vsCompiler.get_declared_struct_size(type: vsCompiler.get_type_handle(bufRes.base_type_id), size: &sz)
desc.size = max(desc.size, sz)
ref.ubo = desc
if !addActiveBufferRanges(ref, symbols: sym, compiler: vsCompiler, resource: bufRes, ubo: true) {
return nil
}
}
} catch {
return nil
}
// fragment UBO binding
do {
if let bufRes = try getResource(fsResources, .uniformBuffer, "fragment shader must use zero or one uniform buffer") {
if fsCompiler.get_decoration(id: bufRes.id, decoration: .descriptorSet) != 0 {
// os_log_error(OE_LOG_DEFAULT, "fragment shader resources must use descriptor set #0");
return nil
}
let binding = Int(fsCompiler.mslGetAutomaticResourceBinding(bufRes.id))
var desc = ref.ubo ?? BufferBindingDescriptor()
desc.bindingFrag = binding
var sz = 0
fsCompiler.get_declared_struct_size(type: fsCompiler.get_type_handle(bufRes.base_type_id), size: &sz)
desc.size = max(desc.size, sz)
ref.ubo = desc
if !addActiveBufferRanges(ref, symbols: sym, compiler: fsCompiler, resource: bufRes, ubo: true) {
return nil
}
}
} catch {
return nil
}
// vertex Push binding
do {
if let bufRes = try getResource(vsResources, .pushConstant, "vertex shader must use zero or one push constant buffer") {
let binding = Int(vsCompiler.mslGetAutomaticResourceBinding(bufRes.id))
var desc = ref.push ?? BufferBindingDescriptor()
desc.bindingVert = binding
var sz = 0
vsCompiler.get_declared_struct_size(type: vsCompiler.get_type_handle(bufRes.base_type_id), size: &sz)
desc.size = max(desc.size, sz)
ref.push = desc
if !addActiveBufferRanges(ref, symbols: sym, compiler: vsCompiler, resource: bufRes, ubo: false) {
return nil
}
}
} catch {
return nil
}
// fragment Push binding
do {
if let bufRes = try getResource(fsResources, .pushConstant, "fragment shader must use zero or one push constant buffer") {
let binding = Int(fsCompiler.mslGetAutomaticResourceBinding(bufRes.id))
var desc = ref.push ?? BufferBindingDescriptor()
desc.bindingFrag = binding
var sz = 0
fsCompiler.get_declared_struct_size(type: fsCompiler.get_type_handle(bufRes.base_type_id), size: &sz)
desc.size = max(desc.size, sz)
ref.push = desc
if !addActiveBufferRanges(ref, symbols: sym, compiler: fsCompiler, resource: bufRes, ubo: false) {
return nil
}
}
} catch {
return nil
}
listSize = 0
fsResources.get_resource_list_for_type(type: .sampledImage, list: &list, size: &listSize)
resources = UnsafeBufferPointer(start: list, count: listSize)
var bindings = 0
for tex in resources {
if fsCompiler.get_decoration(id: tex.id, decoration: .descriptorSet) != 0 {
// os_log_error(OE_LOG_DEFAULT, "fragment shader texture must use descriptor set #0");
return nil
}
let binding = Int(fsCompiler.mslGetAutomaticResourceBinding(tex.id))
guard binding != -1
else {
// no binding
continue
}
if binding >= Constants.maxShaderBindings {
// os_log_error(OE_LOG_DEFAULT, "fragment shader texture binding exceeds %d", Constants.maxShaderBindings);
return nil
}
if bindings & (1 << binding) != 0 {
// os_log_error(OE_LOG_DEFAULT, "fragment shader texture binding %lu already in use", binding);
return nil
}
bindings |= 1 << binding
let name = String(cString: tex.name)
guard let sem = sym.textureSemantic(forName: name)
else {
// os_log_error(OE_LOG_DEFAULT, "invalid texture %{public}s", tex->name);
return nil
}
ref.setBinding(binding, forTextureSemantic: sem.semantic, at: sem.index, name: sem.name)
}
os_log(.debug, log: .default, "%{public}@", ref.debugDescription)
return ref
}
func addActiveBufferRanges(_ ref: ShaderPassReflection, symbols sym: ShaderSymbols, compiler: SPVCompiler, resource res: __spvc_reflected_resource, ubo: Bool) -> Bool {
var rangesPtr: UnsafePointer<spvc_buffer_range>?
var numRanges = 0
compiler.get_active_buffer_ranges(id: res.id, list: &rangesPtr, size: &numRanges)
guard let rangesPtr else { return true }
let ranges = UnsafeBufferPointer(start: rangesPtr, count: numRanges)
for range in ranges {
let name = String(cString: compiler.get_member_name(id: res.base_type_id, index: range.index))
let type = compiler.get_type_handle(compiler.get_type_handle(res.base_type_id)!.get_member_type(index: range.index))!
if let bufferSem = sym.bufferSemantic(forUniformName: name) {
if !bufferSem.validateType(type) {
// os_log_error(OE_LOG_DEFAULT, "invalid type for %{public}s", name);
return false
}
let vecsz = Int(type.vector_size)
let cols = Int(type.columns)
if bufferSem.semantic == .floatParameter {
if !ref.setOffset(range.offset, vecSize: vecsz, forFloatParameterAt: bufferSem.index, name: name, ubo: ubo) {
return false
}
} else {
if !ref.setOffset(range.offset, vecSize: vecsz * cols, forSemantic: bufferSem.semantic, ubo: ubo) {
return false
}
}
} else if let texSem = sym.textureSemantic(forUniformName: name) {
if texSem.semantic == .passOutputSize, texSem.index >= ref.passNumber {
// os_log_error(OE_LOG_DEFAULT, "shader pass #%lu is attempting to use output from self or later pass #%lu", ref.passNumber, texSem.index);
return false
}
if !texSem.validateType(type) {
// os_log_error(OE_LOG_DEFAULT, "invalid type for %{public}s; expected a vec4 of type float", name);
return false
}
if !ref.setOffset(range.offset, forTextureSemantic: texSem.semantic, at: texSem.index, name: name, ubo: ubo) {
return false
}
}
}
return true
}
private enum ValidationError: Error {
case apiError
case unexpectedResource
}
private func validateResources(vsResources: SPVResources, fsResources: SPVResources) throws {
func checkEmpty(_ r: SPVResources, _ t: SPVResourceType) throws {
var list: UnsafePointer<__spvc_reflected_resource>?
var listSize = 0
if r.get_resource_list_for_type(type: t, list: &list, size: &listSize).errorResult != nil {
throw ValidationError.apiError
}
guard listSize == 0
else {
// os_log_error(OE_LOG_DEFAULT, "unexpected resource type in shader %{public}@", @#TYPE); \
throw ValidationError.unexpectedResource
}
}
try checkEmpty(vsResources, .sampledImage)
try checkEmpty(vsResources, .storageBuffer)
try checkEmpty(vsResources, .subpassInput)
try checkEmpty(vsResources, .storageImage)
try checkEmpty(vsResources, .atomicCounter)
try checkEmpty(fsResources, .storageBuffer)
try checkEmpty(fsResources, .subpassInput)
try checkEmpty(fsResources, .storageImage)
try checkEmpty(fsResources, .atomicCounter)
}
}
class ShaderSymbols {
private(set) var floatParameterSemanticMap: [String: ShaderBufferSemanticMap] = [:]
private(set) var textureSemanticMap: [String: ShaderTextureSemanticMap] = [:]
private(set) var textureUniformSemanticMap: [String: ShaderBufferSemanticMap] = [:]
func addTextureSemantic(_ semantic: Compiled.ShaderTextureSemantic, atIndex i: Int, name: String) -> Bool {
if textureSemanticMap[name] != nil {
os_log(.error, log: .default, "pass %lu: alias %{public}@ already exists for texture semantic %{public}@",
i, name, semantic.description as NSString)
return false
}
textureSemanticMap[name] = ShaderTextureSemanticMap(textureSemantic: semantic, index: i, name: name)
return true
}
func addTextureBufferSemantic(_ semantic: Compiled.ShaderBufferSemantic, atIndex i: Int, name: String) -> Bool {
if textureUniformSemanticMap[name] != nil {
os_log(.error, log: .default, "pass %lu: alias %{public}@ already exists for texture buffer semantic %{public}@",
i, name, semantic.description as NSString)
return false
}
textureUniformSemanticMap[name] = ShaderBufferSemanticMap(semantic: semantic, index: i, name: name, baseType: .fp32, vecSize: 4, cols: 1)
return true
}
func addFloatParameterSemantic(atIndex i: Int, name: String) -> Bool {
if floatParameterSemanticMap[name] != nil {
os_log(.error, log: .default, "pass %lu: float parameter %{public}@ already exists",
i, name)
return false
}
floatParameterSemanticMap[name] = ShaderBufferSemanticMap(semantic: .floatParameter,
index: i,
name: name,
baseType: .fp32,
vecSize: 1,
cols: 1)
return true
}
func bufferSemantic(forUniformName name: String) -> ShaderBufferSemanticMap? {
floatParameterSemanticMap[name] ?? Self.semanticUniformNames[name]
}
func textureSemanticIsArray(_ semantic: Compiled.ShaderTextureSemantic) -> Bool {
Self.textureSemanticArrays.contains(semantic)
}
func textureSemantic(forUniformName name: String) -> ShaderBufferSemanticMap? {
textureUniformSemanticMap[name] ?? Self.textureSemanticForUniformName(name)
}
func textureSemantic(forName name: String) -> ShaderTextureSemanticMap? {
textureSemanticMap[name] ?? Self.textureSemanticForName(name)
}
// MARK: - Private functions
private static func textureSemanticForUniformName(_ name: String) -> ShaderBufferSemanticMap? {
for (key, sem) in textureSemanticUniformNames {
if uniformSemanticArrays.contains(sem) {
// An array texture may be referred to as PassOutput0, PassOutput1, etc
if name.hasPrefix(key) {
// TODO: Validate the suffix is a number and within range
let index = Int(name.suffix(from: key.endIndex))
return ShaderBufferSemanticMap(semantic: sem, index: index ?? 0, name: name, baseType: .fp32, vecSize: 4, cols: 1)
}
} else if name == key {
return ShaderBufferSemanticMap(semantic: sem, index: 0, name: name, baseType: .fp32, vecSize: 4, cols: 1)
}
}
return nil
}
private static func textureSemanticForName(_ name: String) -> ShaderTextureSemanticMap? {
for (key, sem) in textureSemanticNames {
if textureSemanticArrays.contains(sem) {
// An array texture may be referred to as PassOutput0, PassOutput1, etc
if name.hasPrefix(key) {
// TODO: Validate the suffix is a number and within range
let index = Int(name.suffix(from: key.endIndex))
return ShaderTextureSemanticMap(textureSemantic: sem, index: index ?? 0, name: name)
}
} else if name == key {
return ShaderTextureSemanticMap(textureSemantic: sem, index: 0, name: name)
}
}
return nil
}
// MARK: - Static variables
static let textureSemanticArrays: Set<Compiled.ShaderTextureSemantic> = [.originalHistory, .passOutput, .passFeedback, .user]
static let uniformSemanticArrays: Set<Compiled.ShaderBufferSemantic> = [.originalHistorySize, .passOutputSize, .passFeedbackSize, .userSize]
static let textureSemanticNames: [String: Compiled.ShaderTextureSemantic] = [
"Original": .original,
"Source": .source,
"OriginalHistory": .originalHistory,
"PassOutput": .passOutput,
"PassFeedback": .passFeedback,
"User": .user,
]
static let textureSemanticUniformNames: [String: Compiled.ShaderBufferSemantic] = [
"OriginalSize": .originalSize,
"SourceSize": .sourceSize,
"OriginalHistorySize": .originalHistorySize,
"PassOutputSize": .passOutputSize,
"PassFeedbackSize": .passFeedbackSize,
"UserSize": .userSize,
]
static let semanticUniformNames: [String: ShaderBufferSemanticMap] = [
"MVP": .init(semantic: .mvp, baseType: .fp32, vecSize: 4, cols: 4),
"OutputSize": .init(semantic: .outputSize, baseType: .fp32, vecSize: 4, cols: 1),
"FinalViewportSize": .init(semantic: .finalViewportSize, baseType: .fp32, vecSize: 4, cols: 1),
"FrameCount": .init(semantic: .frameCount, baseType: .uint32, vecSize: 1, cols: 1),
"FrameDirection": .init(semantic: .frameDirection, baseType: .int32, vecSize: 1, cols: 1),
]
}
class ShaderPassReflection {
let passNumber: Int
var ubo: BufferBindingDescriptor?
var push: BufferBindingDescriptor?
init(passNumber: Int) {
self.passNumber = passNumber
}
private(set) var textures: [Compiled.ShaderTextureSemantic: [Int: ShaderTextureSemanticMeta]] = [
.original: [:],
.source: [:],
.originalHistory: [:],
.passOutput: [:],
.passFeedback: [:],
.user: [:],
]
private(set) var textureUniforms: [Compiled.ShaderBufferSemantic: [Int: ShaderBufferSemanticMeta]] = [
.originalSize: [:],
.sourceSize: [:],
.originalHistorySize: [:],
.passOutputSize: [:],
.passFeedbackSize: [:],
.userSize: [:],
]
private(set) var semantics: [Compiled.ShaderBufferSemantic: ShaderBufferSemanticMeta] = [
.mvp: .init(.mvp),
.outputSize: .init(.outputSize),
.finalViewportSize: .init(.finalViewportSize),
.frameCount: .init(.frameCount),
.frameDirection: .init(.frameDirection),
]
private(set) var floatParameters: [Int: ShaderBufferSemanticMeta] = [:]
func setOffset(_ offset: Int, vecSize: Int, forFloatParameterAt index: Int, name: String, ubo: Bool) -> Bool {
let sem: ShaderBufferSemanticMeta
if let tmp = floatParameters[index] {
sem = tmp
} else {
sem = ShaderBufferSemanticMeta(index: index, name: name)
floatParameters[index] = sem
}
if sem.numberOfComponents != vecSize, sem.uboOffset != nil || sem.pushOffset != nil {
os_log(.error, log: .default, "vertex and fragment shaders have different data type sizes for same parameter #%lu (%lu / %lu)",
index, sem.numberOfComponents, vecSize)
return false
}
if ubo {
if let existing = sem.uboOffset, existing != offset {
os_log(.error, log: .default, "vertex and fragment shaders have different offsets for same parameter #%lu (%lu / %lu)",
index, existing, offset)
return false
}
sem.uboOffset = offset
} else {
if let existing = sem.pushOffset, existing != offset {
os_log(.error, log: .default, "vertex and fragment shaders have different offsets for same parameter #%lu (%lu / %lu)",
index, existing, offset)
return false
}
sem.pushOffset = offset
}
sem.numberOfComponents = vecSize
return true
}
func setOffset(_ offset: Int, vecSize: Int, forSemantic semantic: Compiled.ShaderBufferSemantic, ubo: Bool) -> Bool {
guard let sem = semantics[semantic] else { return false }
if sem.numberOfComponents != vecSize, sem.uboOffset != nil || sem.pushOffset != nil {
os_log(.error, log: .default, "vertex and fragment shaders have different data type sizes for same semantic %@ (%lu / %lu)",
semantic.description as NSString, sem.numberOfComponents, vecSize)
return false
}
if ubo {
if let existing = sem.uboOffset, existing != offset {
os_log(.error, log: .default, "vertex and fragment shaders have different offsets for same semantic %@ (%lu / %lu)",
semantic.description as NSString, existing, offset)
return false
}
sem.uboOffset = offset
} else {
if let existing = sem.pushOffset, existing != offset {
os_log(.error, log: .default, "vertex and fragment shaders have different offsets for same semantic %@ (%lu / %lu)",
semantic.description as NSString, existing, offset)
return false
}
sem.pushOffset = offset
}
sem.numberOfComponents = vecSize
return true
}
func setOffset(_ offset: Int, forTextureSemantic semantic: Compiled.ShaderBufferSemantic, at index: Int, name: String, ubo: Bool) -> Bool {
guard var map = textureUniforms[semantic] else { return false }
var sem: ShaderBufferSemanticMeta
if let tmp = map[index] {
sem = tmp
} else {
sem = ShaderBufferSemanticMeta(index: index, name: name)
map[index] = sem
textureUniforms[semantic] = map
}
if ubo {
if let existing = sem.uboOffset, existing != offset {
os_log(.error, log: .default, "vertex and fragment shaders have different offsets for same semantic %@ #%lu (%lu / %lu)",
semantic.description as NSString, index, existing, offset)
return false
}
sem.uboOffset = offset
} else {
if let existing = sem.pushOffset, existing != offset {
os_log(.error, log: .default, "vertex and fragment shaders have different offsets for same semantic %@ #%lu (%lu / %lu)",
semantic.description as NSString, index, existing, offset)
return false
}
sem.pushOffset = offset
}
return true
}
@discardableResult
func setBinding(_ binding: Int, forTextureSemantic semantic: Compiled.ShaderTextureSemantic, at index: Int, name: String) -> Bool {
guard var map = textures[semantic] else { return false }
var sem: ShaderTextureSemanticMeta
if let tmp = map[index] {
sem = tmp
} else {
sem = ShaderTextureSemanticMeta(index: index, name: name)
map[index] = sem
textures[semantic] = map
}
sem.binding = binding
return true
}
}
extension ShaderPassReflection: CustomDebugStringConvertible {
var debugDescription: String {
var desc = ""
desc.append("\n")
desc.append(" → textures:\n")
for sem in Compiled.ShaderTextureSemantic.allCases {
guard let t = textures[sem] else { continue }
for meta in t.values.sorted(by: { $0.index < $1.index }) where meta.binding != nil {
desc.append(String(format: " %@ (%@ #%lu)\n",
sem.description as NSString, meta.name as NSString, meta.index))
}
}
desc.append("\n")
desc.append(String(format: " → Uniforms (vertex: %@, fragment %@):\n",
ubo?.bindingVert != nil ? "YES" : "NO",
ubo?.bindingFrag != nil ? "YES" : "NO"))
for sem in Compiled.ShaderBufferSemantic.allCases {
if let meta = semantics[sem], let offset = meta.uboOffset {
desc.append(String(format: " %@ (offset: %lu)\n",
sem.description as NSString, offset))
}
}
for sem in Compiled.ShaderBufferSemantic.allCases {
guard let t = textureUniforms[sem] else { continue }
for meta in t.values.sorted(by: { $0.index < $1.index }) where meta.uboOffset != nil {
desc.append(String(format: " %@ (#%lu) (offset: %lu)\n",
meta.name as NSString, meta.index, meta.uboOffset!))
}
}
desc.append("\n")
desc.append(String(format: " → Push (vertex: %@, fragment %@):\n",
push?.bindingVert != nil ? "YES" : "NO",
push?.bindingFrag != nil ? "YES" : "NO"))
for sem in Compiled.ShaderBufferSemantic.allCases {
if let meta = semantics[sem], let offset = meta.pushOffset {
desc.append(String(format: " %@ (offset: %lu)\n",
sem.description as NSString, offset))
}
}
for sem in Compiled.ShaderBufferSemantic.allCases {
guard let t = textureUniforms[sem] else { continue }
for meta in t.values.sorted(by: { $0.index < $1.index }) where meta.pushOffset != nil {
desc.append(String(format: " %@ (#%lu) (offset: %lu)\n",
meta.name as NSString, meta.index, meta.pushOffset!))
}
}
desc.append("\n")
desc.append(" → Parameters:\n")
for meta in floatParameters.values.sorted(by: { $0.index < $1.index }) {
if let offset = meta.uboOffset {
desc.append(String(format: " UBO %@ #%lu (offset: %lu)\n", meta.name, meta.index, offset))
}
if let offset = meta.pushOffset {
desc.append(String(format: " PUSH %@ #%lu (offset: %lu)\n", meta.name, meta.index, offset))
}
}
desc.append("\n")
return desc
}
}