package iron.data; import kha.graphics4.PipelineState; import kha.graphics4.ConstantLocation; import kha.graphics4.TextureUnit; import kha.graphics4.VertexStructure; import kha.graphics4.VertexData; import kha.graphics4.CompareMode; import kha.graphics4.CullMode; import kha.graphics4.BlendingOperation; import kha.graphics4.BlendingFactor; import kha.graphics4.TextureAddressing; import kha.graphics4.TextureFilter; import kha.graphics4.MipMapFilter; import kha.graphics4.VertexShader; import kha.graphics4.FragmentShader; import kha.graphics4.TextureFormat; import kha.graphics4.DepthStencilFormat; import iron.data.SceneFormat; using StringTools; class ShaderData { public var name: String; public var raw: TShaderData; public var contexts: Array = []; #if (lnx_noembed && kha_krom) public static var shaderPath = "../krom-resources/"; public static inline var shaderExt = #if kha_vulkan ".spirv" #elseif (krom_android || krom_wasm) ".essl" #elseif kha_opengl ".glsl" #elseif kha_metal ".metal" #else ".d3d11" #end ; #end public function new(raw: TShaderData, done: ShaderData->Void, overrideContext: TShaderOverride = null) { this.raw = raw; this.name = raw.name; for (c in raw.contexts) contexts.push(null); var contextsLoaded = 0; for (i in 0...raw.contexts.length) { var c = raw.contexts[i]; new ShaderContext(c, function(con: ShaderContext) { contexts[i] = con; contextsLoaded++; if (contextsLoaded == raw.contexts.length) done(this); }, overrideContext); } } public static function parse(file: String, name: String, done: ShaderData->Void, overrideContext: TShaderOverride = null) { Data.getSceneRaw(file, function(format: TSceneFormat) { var raw: TShaderData = Data.getShaderRawByName(format.shader_datas, name); if (raw == null) { trace('Shader data "$name" not found!'); done(null); } new ShaderData(raw, done, overrideContext); }); } public function delete() { for (c in contexts) c.delete(); } public function getContext(name: String): ShaderContext { for (c in contexts) if (c.raw.name == name) return c; return null; } } class ShaderContext { public var raw: TShaderContext; public var pipeState: PipelineState; public var constants: Array; public var textureUnits: Array; public var overrideContext: TShaderOverride; var structure: VertexStructure; var instancingType = 0; public function new(raw: TShaderContext, done: ShaderContext->Void, overrideContext: TShaderOverride = null) { this.raw = raw; #if (!rp_voxels) if (raw.name == "voxel") { done(this); return; } #end this.overrideContext = overrideContext; parseVertexStructure(); compile(done); } public function compile(done: ShaderContext->Void) { if (pipeState != null) pipeState.delete(); pipeState = new PipelineState(); constants = []; textureUnits = []; if (instancingType > 0) { // Instancing var instStruct = new VertexStructure(); instStruct.add("ipos", VertexData.Float3); if (instancingType == 2 || instancingType == 4) { instStruct.add("irot", VertexData.Float3); } if (instancingType == 3 || instancingType == 4) { instStruct.add("iscl", VertexData.Float3); } instStruct.instanced = true; pipeState.inputLayout = [structure, instStruct]; } else { // Regular pipeState.inputLayout = [structure]; } // Depth pipeState.depthWrite = raw.depth_write; pipeState.depthMode = getCompareMode(raw.compare_mode); // Cull pipeState.cullMode = getCullMode(raw.cull_mode); // Blending if (raw.blend_source != null) pipeState.blendSource = getBlendingFactor(raw.blend_source); if (raw.blend_destination != null) pipeState.blendDestination = getBlendingFactor(raw.blend_destination); if (raw.blend_operation != null) pipeState.blendOperation = getBlendingOperation(raw.blend_operation); if (raw.alpha_blend_source != null) pipeState.alphaBlendSource = getBlendingFactor(raw.alpha_blend_source); if (raw.alpha_blend_destination != null) pipeState.alphaBlendDestination = getBlendingFactor(raw.alpha_blend_destination); if (raw.alpha_blend_operation != null) pipeState.alphaBlendOperation = getBlendingOperation(raw.alpha_blend_operation); // Per-target color write mask if (raw.color_writes_red != null) for (i in 0...raw.color_writes_red.length) pipeState.colorWriteMasksRed[i] = raw.color_writes_red[i]; if (raw.color_writes_green != null) for (i in 0...raw.color_writes_green.length) pipeState.colorWriteMasksGreen[i] = raw.color_writes_green[i]; if (raw.color_writes_blue != null) for (i in 0...raw.color_writes_blue.length) pipeState.colorWriteMasksBlue[i] = raw.color_writes_blue[i]; if (raw.color_writes_alpha != null) for (i in 0...raw.color_writes_alpha.length) pipeState.colorWriteMasksAlpha[i] = raw.color_writes_alpha[i]; // Color attachment format if (raw.color_attachments != null) { pipeState.colorAttachmentCount = raw.color_attachments.length; for (i in 0...raw.color_attachments.length) pipeState.colorAttachments[i] = getTextureFormat(raw.color_attachments[i]); } // Depth attachment format if (raw.depth_attachment != null) { #if (krom_windows || krom_linux || krom_darwin || krom_android || krom_ios) pipeState.depthStencilAttachment = getDepthStencilFormat(raw.depth_attachment); #end } // Conservative raster for voxelization if (raw.conservative_raster != null) pipeState.conservativeRasterization = raw.conservative_raster; // Shaders if (raw.shader_from_source) { pipeState.vertexShader = VertexShader.fromSource(raw.vertex_shader); pipeState.fragmentShader = FragmentShader.fromSource(raw.fragment_shader); #if kha_krom // Shader compile error if (pipeState.vertexShader.shader == null || pipeState.fragmentShader.shader == null) { done(null); return; } #end finishCompile(done); } else { #if (lnx_noembed && kha_krom) // Load shaders manually var shadersLoaded = 0; var numShaders = 2; if (raw.geometry_shader != null) numShaders++; if (raw.tesscontrol_shader != null) numShaders++; if (raw.tesseval_shader != null) numShaders++; function loadShader(file: String, type: Int) { var path = ShaderData.shaderPath + file + ShaderData.shaderExt; Data.getBlob(path, function(b: kha.Blob) { if (type == 0) pipeState.vertexShader = new VertexShader([b], [file]); else if (type == 1) pipeState.fragmentShader = new FragmentShader([b], [file]); else if (type == 2) pipeState.geometryShader = new kha.graphics4.GeometryShader([b], [file]); else if (type == 3) pipeState.tessellationControlShader = new kha.graphics4.TessellationControlShader([b], [file]); else if (type == 4) pipeState.tessellationEvaluationShader = new kha.graphics4.TessellationEvaluationShader([b], [file]); shadersLoaded++; if (shadersLoaded >= numShaders) finishCompile(done); }); } loadShader(raw.vertex_shader, 0); loadShader(raw.fragment_shader, 1); if (raw.geometry_shader != null) loadShader(raw.geometry_shader, 2); if (raw.tesscontrol_shader != null) loadShader(raw.tesscontrol_shader, 3); if (raw.tesseval_shader != null) loadShader(raw.tesseval_shader, 4); #elseif lnx_shader_embed pipeState.fragmentShader = kha.Shaders.getFragment(raw.fragment_shader); pipeState.vertexShader = kha.Shaders.getVertex(raw.vertex_shader); if (raw.geometry_shader != null) { pipeState.geometryShader = kha.Shaders.getGeometry(raw.geometry_shader); } finishCompile(done); #else pipeState.fragmentShader = Reflect.field(kha.Shaders, raw.fragment_shader.replace(".", "_")); pipeState.vertexShader = Reflect.field(kha.Shaders, raw.vertex_shader.replace(".", "_")); if (raw.geometry_shader != null) { pipeState.geometryShader = Reflect.field(kha.Shaders, raw.geometry_shader.replace(".", "_")); } if (raw.tesscontrol_shader != null) { pipeState.tessellationControlShader = Reflect.field(kha.Shaders, raw.tesscontrol_shader.replace(".", "_")); } if (raw.tesseval_shader != null) { pipeState.tessellationEvaluationShader = Reflect.field(kha.Shaders, raw.tesseval_shader.replace(".", "_")); } finishCompile(done); #end } } function finishCompile(done: ShaderContext->Void) { // Override specified values if (overrideContext != null) { if (overrideContext.cull_mode != null) { pipeState.cullMode = getCullMode(overrideContext.cull_mode); } } pipeState.compile(); if (raw.constants != null) { for (c in raw.constants) addConstant(c); } if (raw.texture_units != null) { for (tu in raw.texture_units) addTexture(tu); } done(this); } public static function parseData(data: String): VertexData { if (data == "float1") return VertexData.Float1; else if (data == "float2") return VertexData.Float2; else if (data == "float3") return VertexData.Float3; else if (data == "float4") return VertexData.Float4; else if (data == "short2norm") return VertexData.Short2Norm; else if (data == "short4norm") return VertexData.Short4Norm; return VertexData.Float1; } function parseVertexStructure() { structure = new VertexStructure(); var ipos = false; var irot = false; var iscl = false; for (elem in raw.vertex_elements) { #if cpp if (Reflect.field(elem, "name") == "ipos") { ipos = true; continue; } if (Reflect.field(elem, "name") == "irot") { irot = true; continue; } if (Reflect.field(elem, "name") == "iscl") { iscl = true; continue; } #else if (elem.name == "ipos") { ipos = true; continue; } if (elem.name == "irot") { irot = true; continue; } if (elem.name == "iscl") { iscl = true; continue; } #end structure.add(elem.name, parseData(elem.data)); } if (ipos && !irot && !iscl) instancingType = 1; else if (ipos && irot && !iscl) instancingType = 2; else if (ipos && !irot && iscl) instancingType = 3; else if (ipos && irot && iscl) instancingType = 4; } public function delete() { if (pipeState.fragmentShader != null) pipeState.fragmentShader.delete(); if (pipeState.vertexShader != null) pipeState.vertexShader.delete(); if (pipeState.geometryShader != null) pipeState.geometryShader.delete(); if (pipeState.tessellationControlShader != null) pipeState.tessellationControlShader.delete(); if (pipeState.tessellationEvaluationShader != null) pipeState.tessellationEvaluationShader.delete(); pipeState.delete(); } function getCompareMode(s: String): CompareMode { switch (s) { case "always": return CompareMode.Always; case "never": return CompareMode.Never; case "less": return CompareMode.Less; case "less_equal": return CompareMode.LessEqual; case "greater": return CompareMode.Greater; case "greater_equal": return CompareMode.GreaterEqual; case "equal": return CompareMode.Equal; case "not_equal": return CompareMode.NotEqual; default: return CompareMode.Less; } } function getCullMode(s: String): CullMode { switch (s) { case "none": return CullMode.None; case "clockwise": return CullMode.Clockwise; default: return CullMode.CounterClockwise; } } function getBlendingOperation(s: String): BlendingOperation { switch (s) { case "add": return BlendingOperation.Add; case "subtract": return BlendingOperation.Subtract; case "reverse_subtract": return BlendingOperation.ReverseSubtract; case "min": return BlendingOperation.Min; case "max": return BlendingOperation.Max; default: return BlendingOperation.Add; } } function getBlendingFactor(s: String): BlendingFactor { switch (s) { case "blend_one": return BlendingFactor.BlendOne; case "blend_zero": return BlendingFactor.BlendZero; case "source_alpha": return BlendingFactor.SourceAlpha; case "destination_alpha": return BlendingFactor.DestinationAlpha; case "inverse_source_alpha": return BlendingFactor.InverseSourceAlpha; case "inverse_destination_alpha": return BlendingFactor.InverseDestinationAlpha; case "source_color": return BlendingFactor.SourceColor; case "destination_color": return BlendingFactor.DestinationColor; case "inverse_source_color": return BlendingFactor.InverseSourceColor; case "inverse_destination_color": return BlendingFactor.InverseDestinationColor; default: return BlendingFactor.Undefined; } } function getTextureAddresing(s: String): TextureAddressing { switch (s) { case "repeat": return TextureAddressing.Repeat; case "mirror": return TextureAddressing.Mirror; default: return TextureAddressing.Clamp; } } function getTextureFilter(s: String): TextureFilter { switch (s) { case "point": return TextureFilter.PointFilter; case "linear": return TextureFilter.LinearFilter; default: return TextureFilter.AnisotropicFilter; } } function getMipmapFilter(s: String): MipMapFilter { switch (s) { case "no": return MipMapFilter.NoMipFilter; case "point": return MipMapFilter.PointMipFilter; default: return MipMapFilter.LinearMipFilter; } } function getTextureFormat(s: String): TextureFormat { switch (s) { case "RGBA32": return TextureFormat.RGBA32; case "RGBA64": return TextureFormat.RGBA64; case "RGBA128": return TextureFormat.RGBA128; case "DEPTH16": return TextureFormat.DEPTH16; case "R32": return TextureFormat.A32; case "R16": return TextureFormat.A16; case "R8": return TextureFormat.L8; default: return TextureFormat.RGBA32; } } function getDepthStencilFormat(s: String): DepthStencilFormat { switch (s) { case "DEPTH32": return DepthStencilFormat.DepthOnly; case "NONE": return DepthStencilFormat.NoDepthAndStencil; default: return DepthStencilFormat.DepthOnly; } } function addConstant(c: TShaderConstant) { constants.push(pipeState.getConstantLocation(c.name)); } function addTexture(tu: TTextureUnit) { var unit = pipeState.getTextureUnit(tu.name); textureUnits.push(unit); } public function setTextureParameters(g: kha.graphics4.Graphics, unitIndex: Int, tex: TBindTexture) { // This function is called for samplers set using material context var unit = textureUnits[unitIndex]; g.setTextureParameters(unit, tex.u_addressing == null ? TextureAddressing.Repeat : getTextureAddresing(tex.u_addressing), tex.v_addressing == null ? TextureAddressing.Repeat : getTextureAddresing(tex.v_addressing), tex.min_filter == null ? TextureFilter.LinearFilter : getTextureFilter(tex.min_filter), tex.mag_filter == null ? TextureFilter.LinearFilter : getTextureFilter(tex.mag_filter), tex.mipmap_filter == null ? MipMapFilter.NoMipFilter : getMipmapFilter(tex.mipmap_filter)); } }