From f22e506e6d07397c674a0b443fcf8d58d222718d Mon Sep 17 00:00:00 2001 From: Rasmus Brinck Date: Tue, 27 Aug 2024 14:21:47 +0200 Subject: [PATCH 01/23] Adding SDF Metadata for Littera XML Format --- h2d/Font.hx | 8 ++++++++ hxd/fmt/bfnt/FontParser.hx | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/h2d/Font.hx b/h2d/Font.hx index 1949b2d53b..e58abf3656 100644 --- a/h2d/Font.hx +++ b/h2d/Font.hx @@ -185,6 +185,14 @@ class Font { Defaults to `hxd.Charset.getDefault()`. **/ public var charset : hxd.Charset; + /** + Distance range used to generate SDF Font and usefull for some implementations of SDF Font rendering. + **/ + public var distanceRange:Int; + /** + SDF Field type used when generating. + **/ + public var fieldType:String; var glyphs : Map; var nullChar : FontChar; var defaultChar : FontChar; diff --git a/hxd/fmt/bfnt/FontParser.hx b/hxd/fmt/bfnt/FontParser.hx index 885d5dd572..070d276854 100644 --- a/hxd/fmt/bfnt/FontParser.hx +++ b/hxd/fmt/bfnt/FontParser.hx @@ -55,6 +55,12 @@ class FontParser { font.lineHeight = Std.parseInt(xml.node.common.att.lineHeight); font.baseLine = Std.parseInt(xml.node.common.att.base); + // This node contains SDF Metadata + if(xml.hasNode.distanceField) { + font.distanceRange = Std.parseInt(xml.node.distanceField.att.distanceRange); + font.fieldType = Std.string(xml.node.distanceField.att.fieldType); + } + for ( p in xml.node.pages.elements ) { if ( p.att.id == "0" ) { resolveTileWithFallback(p.att.file); From 7b42819b643fffd835b47b34171717104ce864e9 Mon Sep 17 00:00:00 2001 From: Leo Bergman Date: Wed, 15 Jan 2025 14:55:19 +0000 Subject: [PATCH 02/23] feat: Support ktx2 compressed textures --- hxd/CompressedTextureFormat.hx | 22 + hxd/PixelFormat.hx | 6 +- hxd/Pixels.hx | 54 +- hxd/net/BinaryLoader.hx | 4 +- hxd/res/Image.hx | 23 + hxd/res/Ktx2.hx | 999 +++++++++++++++++++++++++++++++++ 6 files changed, 1098 insertions(+), 10 deletions(-) create mode 100644 hxd/CompressedTextureFormat.hx create mode 100644 hxd/res/Ktx2.hx diff --git a/hxd/CompressedTextureFormat.hx b/hxd/CompressedTextureFormat.hx new file mode 100644 index 0000000000..656309d76c --- /dev/null +++ b/hxd/CompressedTextureFormat.hx @@ -0,0 +1,22 @@ +package hxd; + +enum abstract ASTC_FORMAT(Int) from Int to Int { + final RGBA_4x4 = 0x93B0; +} + +enum abstract DXT_FORMAT(Int) from Int to Int { + final RGB_DXT1 = 0x83F0; + final RGBA_DXT1 = 0x83F1; + final RGBA_DXT3 = 0x83F2; + final RGBA_DXT5 = 0x83F3; +} + +enum abstract ETC_FORMAT(Int) from Int to Int { + final RGB_ETC1 = 0x8D64; + final RGBA_ETC2 = 0x9278; +} + +enum abstract BPTC_FORMAT(Int) from Int to Int { + final RGB_BPTC_UNSIGNED = 0x8E8F; + final RGBA_BPTC = 0x8E8C; +} diff --git a/hxd/PixelFormat.hx b/hxd/PixelFormat.hx index 6ad043c686..ebbf9438c4 100644 --- a/hxd/PixelFormat.hx +++ b/hxd/PixelFormat.hx @@ -23,9 +23,11 @@ enum PixelFormat { RG16U; RGB16U; RGBA16U; - S3TC( v : Int ); + ASTC(v:Int); + ETC(v:Int); + S3TC(v:Int); Depth16; Depth24; Depth24Stencil8; Depth32; -} \ No newline at end of file +} diff --git a/hxd/Pixels.hx b/hxd/Pixels.hx index b56a0f75da..dc61fe4cd4 100644 --- a/hxd/Pixels.hx +++ b/hxd/Pixels.hx @@ -391,6 +391,8 @@ class Pixels { this.bytes = nbytes; case [S3TC(a),S3TC(b)] if( a == b ): + case [ASTC(a),ASTC(b)] if( a == b ): + case [ETC(a),ETC(b)] if( a == b ): // nothing #if (hl && hl_ver >= "1.10") @@ -536,8 +538,33 @@ class Pixels { public static function calcDataSize( width : Int, height : Int, format : PixelFormat ) { return switch( format ) { - case S3TC(_): - (((height + 3) >> 2) << 2) * calcStride(width, format); + case S3TC(n): + var w = (width + 3) >> 2; + var h = (height + 3) >> 2; + var blocks = w * h; // Total number of blocks + if( n == 3 ) { // DXT5 + blocks * 16; // 16 bytes per block + } else if( n == 1 || n == 4 ) { + blocks * 8; // DXT1 or BC4, 8 bytes per block + } else { + blocks * 16; // DXT3 or BC5, 16 bytes per block, but handling like DXT5 for simplicity + } + case ASTC(n): + var w = (width + 3) >> 2; + var h = (height + 3) >> 2; + w * h * 16; + case ETC(n): + if( n == 0 ) { // RGB_ETC1_Format or RGB_ETC2_Format + var w = (width + 3) >> 2; + var h = (height + 3) >> 2; + w * h * 8; + } else if( n == 1 || n == 2 ) { // RGBA_ETC2_EAC_Format + var w = (width + 3) >> 2; + var h = (height + 3) >> 2; + w * h * 16; + } else { + throw "Unsupported ETC format"; + } default: height * calcStride(width, format); } @@ -559,11 +586,26 @@ class Pixels { case RGB32F: 12; case RGB10A2: 4; case RG11B10UF: 4; + case ASTC(n): + var blocks = ((width + 3) >> 2) * 16; + blocks << 4; + case ETC(n): + if( n == 0 ) { // ETC1 and ETC2 RGB + ((width + 3) >> 2) << 3; + } else if( n == 1 ) { // ETC2 EAC RGBA + ((width + 3) >> 2) << 4; + } else { + throw "Unsupported ETC format"; + } case S3TC(n): var blocks = (width + 3) >> 2; - if( n == 1 || n == 4 ) - return blocks << 1; - return blocks << 2; + if( n == 3 ) { // DXT5 + blocks << 4; // 16 bytes per block + } else if( n == 1 || n == 4 ) { + blocks << 1; // DXT1 or BC4, 8 bytes per block + } else { + blocks << 2; // DXT3 or BC5, 16 bytes per block, but handling like DXT5 for simplicity + } case Depth16: 2; case Depth24: 3; case Depth24Stencil8, Depth32: 4; @@ -605,7 +647,7 @@ class Pixels { channel.toInt() * 4; case RGB10A2, RG11B10UF: throw "Bit packed format"; - case S3TC(_), Depth16, Depth24, Depth24Stencil8, Depth32: + case S3TC(_), ASTC(_), ETC(_), Depth16, Depth24, Depth24Stencil8, Depth32: throw "Not supported"; } } diff --git a/hxd/net/BinaryLoader.hx b/hxd/net/BinaryLoader.hx index d10a0c37fa..24bb581172 100644 --- a/hxd/net/BinaryLoader.hx +++ b/hxd/net/BinaryLoader.hx @@ -18,7 +18,7 @@ class BinaryLoader { throw msg; } - public function load() { + public function load(raw = false) { #if js var xhr = new js.html.XMLHttpRequest(); @@ -32,7 +32,7 @@ class BinaryLoader { onError(xhr.statusText); return; } - onLoaded(haxe.io.Bytes.ofData(xhr.response)); + onLoaded(raw ? xhr.response : haxe.io.Bytes.ofData(xhr.response)); } xhr.onprogress = function(e) { diff --git a/hxd/res/Image.hx b/hxd/res/Image.hx index 76a3f7206d..75c04190eb 100644 --- a/hxd/res/Image.hx +++ b/hxd/res/Image.hx @@ -8,6 +8,7 @@ enum abstract ImageFormat(Int) { var Dds = 4; var Raw = 5; var Hdr = 6; + var Ktx2 = 7; /* Tells if we might not be able to directly decode the image without going through a loadBitmap async call. @@ -35,6 +36,7 @@ enum abstract ImageFormat(Int) { case Dds: "DDS"; case Raw: "RAW"; case Hdr: "HDR"; + case Ktx2: "KTX2"; }; } } @@ -242,6 +244,22 @@ class Image extends Resource { throw entry.path + " has unsupported 4CC " + fid; } + #if js + case 0x4273: + throw 'Use .ktx2 files for GPU compressed textures instead of .basis'; + case 0x4BAB: + final ktx2 = hxd.res.Ktx2.readFile(new haxe.io.BytesInput(@:privateAccess f.cache)); + inf.pixelFormat = switch ktx2.dfd.colorModel { + case hxd.res.Ktx2.DFDModel.ETC1S: ETC(hxd.res.Ktx2.TranscoderFormat.ETC1); + case hxd.res.Ktx2.DFDModel.UASTC: ASTC(hxd.res.Ktx2.TranscoderFormat.ASTC_4x4); + default: throw 'Unsupported colorModel in ktx2 file ${ktx2.dfd.colorModel}'; + } + inf.mipLevels = ktx2.header.levelCount; + inf.width = ktx2.header.pixelWidth; + inf.height = ktx2.header.pixelHeight; + inf.dataFormat = Ktx2; + #end + case 0x3F23: // HDR RADIANCE inf.dataFormat = Hdr; @@ -435,6 +453,9 @@ class Image extends Resource { case Hdr: var data = hxd.fmt.hdr.Reader.decode(entry.getBytes(), false); pixels = new hxd.Pixels(data.width, data.height, data.bytes, inf.pixelFormat); + case Ktx2: + var bytes = entry.getBytes(); + pixels = new hxd.Pixels(inf.width, inf.height, bytes, inf.pixelFormat); } if (fmt != null) pixels.convert(fmt); @@ -610,6 +631,8 @@ class Image extends Resource { pos += size; } } + case Ktx2: + throw 'Ktx2 loading using heaps resource system not implemented'; default: for (layer in 0...tex.layerCount) { for (mip in 0...inf.mipLevels) { diff --git a/hxd/res/Ktx2.hx b/hxd/res/Ktx2.hx new file mode 100644 index 0000000000..d67fb9fb56 --- /dev/null +++ b/hxd/res/Ktx2.hx @@ -0,0 +1,999 @@ +package hxd.res; +#if js +import haxe.io.UInt8Array; +using Lambda; +/** + Ktx2 file parser. +**/ +class Ktx2 { + static inline final BYTE_INDEX_ERROR = 'ktx2 files with a file size exceeding 32 bit address space is not supported'; + + /** + Read ktx2 file + + @param bytes BytesInput containing ktx2 file data + + @return Parsed ktx2 file + **/ + public static function readFile( bytes : haxe.io.BytesInput ) : Ktx2File { + final header = readHeader(bytes); + final levels = readLevels(bytes, header.levelCount); + final dfd = readDfd(bytes); + final file : Ktx2File = { + header: header, + levels: levels, + dfd: dfd, + data: new js.lib.Uint8Array(@:privateAccess bytes.b), + supercompressionGlobalData: null, + } + return file; + } + + public static function readHeader( bytes : haxe.io.BytesInput) : KTX2Header { + final ktx2Id = [ + // '´', 'K', 'T', 'X', '2', '0', 'ª', '\r', '\n', '\x1A', '\n' + 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A, + ]; + + final matching = ktx2Id.mapi((i, id) -> id == bytes.readByte()); + + if( matching.contains(false) ) { + throw 'Invalid KTX2 header'; + } + final header :KTX2Header = { + vkFormat: bytes.readInt32(), + typeSize: bytes.readInt32(), + pixelWidth: bytes.readInt32(), + pixelHeight: bytes.readInt32(), + pixelDepth: bytes.readInt32(), + layerCount: bytes.readInt32(), + faceCount: bytes.readInt32(), + levelCount: bytes.readInt32(), + supercompressionScheme: bytes.readInt32(), + + dfdByteOffset: bytes.readInt32(), + dfdByteLength: bytes.readInt32(), + kvdByteOffset: bytes.readInt32(), + kvdByteLength: bytes.readInt32(), + sgdByteOffset: { + final val = bytes.read(8).getInt64(0); + if( val.high>0 ) { + throw BYTE_INDEX_ERROR; + } + val.low; + }, + sgdByteLength: { + final val = bytes.read(8).getInt64(0); + if( val.high>0 ) { + throw BYTE_INDEX_ERROR; + } + val.low; + } + } + + if( header.pixelDepth>0 ) { + throw 'Failed to parse KTX2 file - Only 2D textures are currently supported.'; + } + if( header.layerCount>1 ) { + throw 'Failed to parse KTX2 file - Array textures are not currently supported.'; + } + if( header.faceCount>1 ) { + throw 'Failed to parse KTX2 file - Cube textures are not currently supported.'; + } + return header; + } + + static function readLevels( bytes : haxe.io.BytesInput, levelCount : Int) : Array { + levelCount = hxd.Math.imax(1, levelCount); + final length = levelCount * 3 * (2 * 4); + final level = bytes.read(length); + final levels:Array = []; + + while( levelCount-- > 0 ) { + levels.push({ + byteOffset: { + final val = level.getInt64(0); + if( val.high>0 ) { + throw BYTE_INDEX_ERROR; + } + val.low; + }, + byteLength: { + final val = level.getInt64(8); + if( val.high>0 ) { + throw BYTE_INDEX_ERROR; + } + val.low; + }, + uncompressedByteLength: { + final val = level.getInt64(16); + if( val.high>0 ) { + throw BYTE_INDEX_ERROR; + } + val.low; + }, + }); + } + return levels; + } + + static function readDfd( bytes : haxe.io.BytesInput) : KTX2DFD { + final totalSize = bytes.readInt32(); + final vendorId = bytes.readInt16(); + final descriptorType = bytes.readInt16(); + final versionNumber = bytes.readInt16(); + final descriptorBlockSize = bytes.readInt16(); + final numSamples = Std.int((descriptorBlockSize-24)/16); + final dfdBlock : KTX2DFD = { + vendorId:vendorId, + descriptorType: descriptorType, + versionNumber: versionNumber, + descriptorBlockSize: descriptorBlockSize, + colorModel: bytes.readByte(), + colorPrimaries: bytes.readByte(), + transferFunction: bytes.readByte(), + flags: bytes.readByte(), + texelBlockDimension: { + x: bytes.readByte()+1, + y: bytes.readByte()+1, + z: bytes.readByte()+1, + w: bytes.readByte()+1, + }, + bytesPlane: [ + bytes.readByte() /* bytesPlane0 */, + bytes.readByte() /* bytesPlane1 */, + bytes.readByte() /* bytesPlane2 */, + bytes.readByte() /* bytesPlane3 */, + bytes.readByte() /* bytesPlane4 */, + bytes.readByte() /* bytesPlane5 */, + bytes.readByte() /* bytesPlane6 */, + bytes.readByte() /* bytesPlane7 */, + ], + numSamples: numSamples, + samples: [ + for( i in 0...numSamples ) { + final bitOffset = bytes.readUInt16(); + final bitLength = bytes.readByte()+1; + final channelType = bytes.readByte(); + final channelFlags = (channelType & 0xf0)>>4; + final samplePosition = [ + bytes.readByte() /* samplePosition0 */, + bytes.readByte() /* samplePosition1 */, + bytes.readByte() /* samplePosition2 */, + bytes.readByte() /* samplePosition3 */, + ]; + final sampleLower = bytes.readUInt16()+bytes.readUInt16(); + final sampleUpper = bytes.readUInt16()+bytes.readUInt16(); + final sample : KTX2Sample = { + bitOffset: bitOffset, + bitLength: bitLength, + channelType: channelType & 0x0F, + channelFlags: channelFlags, + samplePosition: samplePosition, + sampleLower: sampleLower, + sampleUpper: sampleUpper, + }; + sample; + } + ], + } + return dfdBlock; + } +} + +class Ktx2Decoder { + public static var mscTranscoder : Dynamic; + public static var workerLimit = 4; + + static var _workerNextTaskID = 1; + static var _workerSourceURL : String; + static var _workerConfig : BasisWorkerConfig; + static var _workerPool : Array = []; + static var _transcoderPending : js.lib.Promise; + static var _transcoderBinary : haxe.io.Bytes; + static var _transcoderScript : String; + static var _transcoderLoading : js.lib.Promise<{ script : String, wasm : haxe.io.Bytes }>; + + public static function getTexture(bytes : haxe.io.BytesInput, cb : (texture : h3d.mat.Texture) -> Void) { + createTexture(bytes, cb); + } + + static function detectSupport(fmt : KtxTranscodeTarget) { + final driver : h3d.impl.GlDriver = cast h3d.Engine.getCurrent().driver; + return { + astcSupported: driver.textureSupport.astc, + etc1Supported: driver.textureSupport.etc1, + etc2Supported: driver.textureSupport.etc2, + dxtSupported: driver.textureSupport.dxt, + bptcSupported: driver.textureSupport.bptc, + } + } + + static function getWorker() { + return initTranscoder().then(val -> { + if( _workerPool.length < workerLimit ) { + final worker = new js.html.Worker(_workerSourceURL); + final workerTask : WorkerTask = { + worker: worker, + callbacks: new haxe.ds.IntMap(), + taskCosts: new haxe.ds.IntMap(), + taskLoad: 0, + } + worker.postMessage({ + type: 'init', + config: _workerConfig, + transcoderBinary: _transcoderBinary, + }); + + worker.onmessage = e -> { + var message = e.data; + switch( message.type ) { + case 'transcode': + workerTask.callbacks.get(message.id).resolve(message); + case 'error': + workerTask.callbacks.get(message.id).reject(message); + default: + throw 'Ktx2Loader: Unexpected message, "${message.type}"'; + } + }; + _workerPool.push(workerTask); + } else { + _workerPool.sort((a, b) -> a.taskLoad > b.taskLoad ? -1 : 1); + } + + return _workerPool[_workerPool.length-1]; + }); + } + + static function createTexture( buffer : haxe.io.BytesInput, cb : (texture:h3d.mat.Texture) -> Void) { + final ktx = Ktx2.readFile(buffer); + + final w = ktx.header.pixelWidth; + final h = ktx.header.pixelHeight; + + final transcodeTarget = switch ktx.dfd.colorModel { + case hxd.res.Ktx2.DFDModel.ETC1S: + KtxTranscodeTarget.ETC1S({}, { + fmt: CompressedFormat.ETC1, + alpha: ktx.dfd.hasAlpha(), + needsPowerOfTwo: true, + }); + case hxd.res.Ktx2.DFDModel.UASTC: + KtxTranscodeTarget.UASTC({}, { + fmt: CompressedFormat.ASTC, + alpha: ktx.dfd.hasAlpha(), + needsPowerOfTwo: true, + }); + default: throw 'Unsupported colorModel in ktx2 file ${ktx.dfd.colorModel}'; + } + _workerConfig = detectSupport(transcodeTarget); + getWorker().then(task -> { + final worker = task.worker; + final taskID = _workerNextTaskID++; + + final textureDone = new js.lib.Promise((resolve, reject) -> { + task.callbacks.set(taskID, { + resolve: resolve, + reject: reject, + }); + task.taskCosts.set(taskID, buffer.length); + task.taskLoad += task.taskCosts.get(taskID); + buffer.position = 0; + final bytes = buffer.readAll().getData(); + worker.postMessage({type: 'transcode', id: taskID, buffer: bytes}, [bytes]); + }); + + textureDone.then(( message : BasisWorkerMessage )-> { + if( message.type == 'error' ) { + throw 'Unable to decode ktx2 file: ${message.error}'; + } + + final w = message.data.width; + final h = message.data.height; + final create = (fmt:hxd.PixelFormat) -> { + if( ktx.header.faceCount > 1 || ktx.header.layerCount > 1 ) { + // TODO: Handle cube texture + throw 'Multi texture ktx2 files not supported'; + } + final face = message.data.faces[0]; + final mipmaps:Array = face.mipmaps; + final texture = new h3d.mat.Texture(w, h, null, fmt); + var level = 0; + for (mipmap in mipmaps) { + final bytes = haxe.io.Bytes.ofData(cast mipmap.data); + final pixels = new hxd.Pixels(mipmap.width, mipmap.height, bytes, fmt); + texture.uploadPixels(pixels, level); + level++; + } + if(mipmaps.length>1) { + texture.flags.set(MipMapped); + texture.mipMap = Linear; + } + texture; + } + final texture = switch message.data.format { + case EngineFormat.RGBA_ASTC_4x4_Format: + create(hxd.PixelFormat.ASTC(10)); + case EngineFormat.RGB_BPTC_UNSIGNED_Format: + create(hxd.PixelFormat.S3TC(6)); + case EngineFormat.RGBA_BPTC_Format: + create(hxd.PixelFormat.S3TC(7)); + case EngineFormat.RGBA_S3TC_DXT5_Format: + create(hxd.PixelFormat.S3TC(3)); + case EngineFormat.RGB_ETC1_Format: + create(hxd.PixelFormat.ETC(0)); + case EngineFormat.RGBA_ETC2_EAC_Format: + create(hxd.PixelFormat.ETC(1)); + default: + throw 'Ktx2Loader: No supported format available.'; + } + + if( task != null && taskID > 0 ) { + task.taskLoad -= task.taskCosts.get(taskID); + task.callbacks.remove(taskID); + task.taskCosts.remove(taskID); + } + cb(texture); + }); + }); + + } + + static function initTranscoder() { + _transcoderLoading = if( _transcoderLoading == null ) { + // Load transcoder wrapper. + final jsLoader = new hxd.net.BinaryLoader('vendor/basis_transcoder.js'); + final jsContent = new js.lib.Promise((resolve, reject) -> { + jsLoader.onLoaded = resolve; + jsLoader.onError = reject; + jsLoader.load(); + }); + // Load transcoder WASM binary. + final binaryLoader = new hxd.net.BinaryLoader('vendor/basis_transcoder.wasm'); + final binaryContent = new js.lib.Promise((resolve, reject) -> { + binaryLoader.onLoaded = resolve; + binaryLoader.onError = reject; + binaryLoader.load(true); + }); + js.lib.Promise.all([jsContent, binaryContent]).then(arr -> {script:arr[0].toString(), wasm:arr[1]}); + } else { + _transcoderLoading; + } + + _transcoderPending = _transcoderLoading.then(o -> { + _transcoderScript = o.script; + _transcoderBinary = o.wasm; + final transcoderFormat = Type.getClassFields(TranscoderFormat).map(f -> '"$f": ${Reflect.field(TranscoderFormat, f)},\n').fold((curr, acc) -> '$acc\t$curr', '{\n') + '}'; + final basisFormat = Type.allEnums(BasisFormat).fold((curr, acc) -> '$acc\t"${curr.getName()}": ${curr.getIndex()},\n', '{\n') + '}'; + final engineFormat = Type.getClassFields(EngineFormat).map(f -> '"$f": ${Reflect.field(EngineFormat, f)},\n').fold((curr, acc) -> '$acc\t$curr', '{\n') + '}'; + final engineType = Type.getClassFields(EngineType).map(f -> '"$f": ${Reflect.field(EngineType, f)},\n').fold((curr, acc) -> '$acc\t$curr', '{\n') + '}'; + final body = [ + '/* constants */', + 'let _EngineFormat = $engineFormat', + 'let _EngineType = $engineType', + 'let _TranscoderFormat = $transcoderFormat', + 'let _BasisFormat = $basisFormat', + '/* basis_transcoder.js */', + _transcoderScript, + '/* worker */', + basisWorker() + ].join('\n'); + + _workerSourceURL = js.html.URL.createObjectURL(new js.html.Blob([body])); + }); + return _transcoderPending; + } +} + +typedef Ktx2File = { + header : KTX2Header, + levels : Array, + dfd : KTX2DFD, + data : js.lib.Uint8Array, + supercompressionGlobalData : KTX2SupercompressionGlobalData, +} + +enum abstract SuperCompressionScheme(Int) from Int to Int { + final NONE = 0; + final BASISLZ = 1; + final ZSTANDARD = 2; + final ZLIB = 3; +} + +enum abstract DFDModel(Int) from Int to Int { + final ETC1S = 163; + final UASTC = 166; +} + +enum abstract DFDChannel_ETC1S(Int) from Int to Int { + final RGB = 0; + final RRR = 3; + final GGG = 4; + final AAA = 15; +} + +enum abstract DFDChannel_UASTC(Int) from Int to Int { + final RGB = 0; + final RGBA = 3; + final RRR = 4; + final RRRG = 5; +} + +enum abstract DFDTransferFunction(Int) from Int to Int { + final LINEAR = 1; + final SRGB = 2; +} + +enum abstract SupercompressionScheme(Int) from Int to Int { + public final None = 0; + public final BasisLZ = 1; + public final ZStandard = 2; + public final ZLib = 3; +} + +/** @internal */ +@:structInit class KTX2Header { + public final vkFormat : Int; + public final typeSize : Int; + public final pixelWidth : Int; + public final pixelHeight : Int; + public final pixelDepth : Int; + public final layerCount : Int; + public final faceCount : Int; + public final levelCount : Int; + public final supercompressionScheme : Int; + public final dfdByteOffset : Int; + public final dfdByteLength : Int; + public final kvdByteOffset : Int; + public final kvdByteLength : Int; + public final sgdByteOffset : Int; + public final sgdByteLength : Int; + + public function needZSTDDecoder() { + return supercompressionScheme == SupercompressionScheme.ZStandard; + } +} + +/** @internal */ +typedef KTX2Level = { + /** + Byte offset. According to spec this should be 64 bit, but since a lot of byte code in haxe is using regular 32 bit Int for indexing, + supporting files to large to fit in 32bit space is complicated and should not be needed for individual game assets. + **/ + final byteOffset : Int; + final byteLength : Int; + final uncompressedByteLength : Int; +} + +typedef KTX2Sample = { + final bitOffset : Int; + final bitLength : Int; + final channelType : Int; + final channelFlags : Int; + final samplePosition : Array; + final sampleLower : Int; + final sampleUpper : Int; +} + +/** @internal */ +@:structInit class KTX2DFD { + public final vendorId : Int; + public final descriptorType : Int; + public final versionNumber : Int; + public final descriptorBlockSize : Int; + public final colorModel : Int; + public final colorPrimaries : Int; + public final transferFunction : Int; + public final flags : Int; + public final texelBlockDimension : { + x : Int, + y : Int, + z : Int, + w : Int, + }; + public final bytesPlane : Array; + public final numSamples : Int; + public final samples : Array; + + public function hasAlpha() { + return switch colorModel { + case hxd.res.Ktx2.DFDModel.ETC1S: + numSamples == 2 && (samples[0].channelType == DFDChannel_ETC1S.AAA || samples[1].channelType == DFDChannel_ETC1S.AAA); + case hxd.res.Ktx2.DFDModel.UASTC: + samples[0].channelType == DFDChannel_UASTC.RGBA; + default: throw 'Unsupported colorModel in ktx2 file ${colorModel}'; + } + } + + public function isInGammaSpace() { + return transferFunction == DFDTransferFunction.SRGB; + } +} + +/** @internal */ +typedef KTX2ImageDesc = { + final imageFlags : Int; + final rgbSliceByteOffset : Int; + final rgbSliceByteLength : Int; + final alphaSliceByteOffset : Int; + final alphaSliceByteLength : Int; +} + +/** @internal */ +typedef KTX2SupercompressionGlobalData = { + final endpointCount : Int; + final selectorCount : Int; + final endpointsByteLength : Int; + final selectorsByteLength : Int; + final tablesByteLength : Int; + final extendedByteLength : Int; + final imageDescs : Array; + final endpointsData : haxe.io.UInt8Array; + final selectorsData : haxe.io.UInt8Array; + final tablesData : haxe.io.UInt8Array; + final extendedData : haxe.io.UInt8Array; +} + +@:keep +class TranscoderFormat { + public static final ETC1 = 0; + public static final ETC2 = 1; + public static final BC1 = 2; + public static final BC3 = 3; + public static final BC4 = 4; + public static final BC5 = 5; + public static final BC7_M6_OPAQUE_ONLY = 6; + public static final BC7_M5 = 7; + + public static final ASTC_4x4 = 10; + public static final ATC_RGB = 11; + public static final ATC_RGBA_INTERPOLATED_ALPHA = 12; + public static final RGBA32 = 13; + public static final RGB565 = 14; + public static final BGR565 = 15; + public static final RGBA4444 = 16; + public static final BC6H = 22; + public static final RGB_HALF = 24; + public static final RGBA_HALF = 25; +} + +enum TranscoderType { + cTFETC1; + cTFETC2; // Not used + cTFBC1; + cTFBC3; + cTFBC4; // Not used + cTFBC5; // Not used + cTFBC7_M6_OPAQUE_ONLY; // Not used + cTFBC7_M5; // Not used + cTFPVRTC1_4_RGB; // Not used + cTFPVRTC1_4_RGBA; // Not used + cTFASTC_4x4; + cTFATC_RGB1; // Not used + cTFATC_RGBA_INTERPOLATED_ALPHA2; // Not used + cTFRGBA321; + cTFRGB5654; // Not used + cTFBGR5655; // Not used + cTFRGBA44446; // Not used +} + +@:keep +enum BasisFormat { + ETC1S; + UASTC; + UASTC_HDR; +} + +@:keep +class EngineFormat { + public static final RGBAFormat = 0x03FF; + public static final RGBA8Format = 0x8058; + public static final R8Format = 0x8229; + public static final RG8Format = 0x822b; + public static final RGBA_ASTC_4x4_Format = CompressedTextureFormat.ASTC_FORMAT.RGBA_4x4; + public static final RGB_BPTC_UNSIGNED_Format = CompressedTextureFormat.BPTC_FORMAT.RGB_BPTC_UNSIGNED; + public static final RGBA_BPTC_Format = CompressedTextureFormat.BPTC_FORMAT.RGBA_BPTC; + public static final RGB_S3TC_DXT1_Format = CompressedTextureFormat.DXT_FORMAT.RGB_DXT1; + public static final RGBA_S3TC_DXT1_Format = CompressedTextureFormat.DXT_FORMAT.RGBA_DXT1; + public static final RGBA_S3TC_DXT5_Format = CompressedTextureFormat.DXT_FORMAT.RGBA_DXT5; + public static final RGB_ETC1_Format = CompressedTextureFormat.ETC_FORMAT.RGB_ETC1; + public static final RGBA_ETC2_EAC_Format = CompressedTextureFormat.ETC_FORMAT.RGBA_ETC2; + public static final RGB_ETC2_Format = 0x9274; +} + +@:keep +class EngineType { + public static final UnsignedByteType = 1009; + public static final FloatType = 1015; + public static final HalfFloatType = 1016; +} + +/** + * Defines a mipmap level + */ +@:structInit class MipmapLevel { + /** + * The data of the mipmap level + */ + public var data : Null = null; + + /** + * The width of the mipmap level + */ + public final width : Int; + + /** + * The height of the mipmap level + */ + public final height : Int; +} + +enum KtxTranscodeTarget { + ETC1S(options : ETC1SDecoderOptions, caps : Ktx2Caps); + UASTC(options : UASTCDecoderOptions, caps : Ktx2Caps); +} + +@:structInit class KtxTranscodeConfig { + public final transcodeFormat :TranscodeTarget; + public final engineFormat :Int; + public final engineType = EngineType.UnsignedByteType; + public final roundToMultiple4 = true; +} + + +@:structInit class Ktx2Caps { + public final fmt : CompressedFormat; + + public final alpha : Null = null; + + public final needsPowerOfTwo = true; +} + +enum CompressedFormat { + ETC2; + ETC1; + S3TC; + ASTC; + BPTC; +} + +/** +* Options passed to the KTX2 decode function +*/ +@:structInit class UASTCDecoderOptions { + /** use RGBA format if ASTC and BC7 are not available as transcoded format */ + public final useRGBAIfASTCBC7NotAvailableWhenUASTC = false; + + /** force to always use (uncompressed) RGBA for transcoded format */ + public final forceRGBA = false; + + /** force to always use (uncompressed) R8 for transcoded format */ + public final forceR8 = false; + + /** force to always use (uncompressed) RG8 for transcoded format */ + public final forceRG8 = false; +} + +@:structInit class ETC1SDecoderOptions { + public final forceRGBA = false; +} + +enum TranscodeTarget { + ASTC_4X4_RGBA; + BC7_RGBA; + BC3_RGBA; + BC1_RGB; + ETC2_RGBA; + ETC1_RGB; + RGBA32; + R8; + RG8; +} + +typedef WorkerTask = { + worker : js.html.Worker, + callbacks : haxe.ds.IntMap<{ resolve : (value : Dynamic) -> Void, reject : (reason : Dynamic) -> Void }>, + taskCosts : haxe.ds.IntMap, + taskLoad : Int, +} + +typedef BasisWorkerConfig = { + astcSupported : Bool, + etc1Supported : Bool, + etc2Supported : Bool, + dxtSupported : Bool, +} + +function basisWorker() { + return " + let config; + let transcoderPending; + let BasisModule; + + const EngineFormat = _EngineFormat; + const EngineType = _EngineType; + const TranscoderFormat = _TranscoderFormat; + const BasisFormat = _BasisFormat; + + self.addEventListener( 'message', function ( e ) { + const message = e.data; + switch ( message.type ) { + case 'init': + config = message.config; + init( message.transcoderBinary ); + break; + case 'transcode': + transcoderPending.then( () => { + try { + const { faces, buffers, width, height, hasAlpha, format, type, dfdFlags } = transcode( message.buffer ); + self.postMessage( { type: 'transcode', id: message.id, data: { faces, width, height, hasAlpha, format, type, dfdFlags } }, buffers ); + } catch ( error ) { + console.error( error ); + self.postMessage( { type: 'error', id: message.id, error: error.message } ); + } + } ); + break; + } + } ); + + function init( wasmBinary ) { + transcoderPending = new Promise( ( resolve ) => { + BasisModule = { wasmBinary, onRuntimeInitialized: resolve }; + BASIS( BasisModule ); // eslint-disable-line no-undef + } ).then( () => { + BasisModule.initializeBasis(); + if ( BasisModule.KTX2File === undefined ) { + console.warn( 'KTX2Loader: Please update Basis Universal transcoder.' ); + } + } ); + } + + function transcode( buffer ) { + const ktx2File = new BasisModule.KTX2File( new Uint8Array( buffer ) ); + function cleanup() { + ktx2File.close(); + ktx2File.delete(); + } + + if ( ! ktx2File.isValid() ) { + cleanup(); + throw new Error( 'KTX2Loader: Invalid or unsupported .ktx2 file' ); + } + let basisFormat; + if ( ktx2File.isUASTC() ) { + basisFormat = BasisFormat.UASTC; + } else if ( ktx2File.isETC1S() ) { + basisFormat = BasisFormat.ETC1S; + } else if ( ktx2File.isHDR() ) { + basisFormat = BasisFormat.UASTC_HDR; + } else { + throw new Error( 'KTX2Loader: Unknown Basis encoding' ); + } + const width = ktx2File.getWidth(); + const height = ktx2File.getHeight(); + const layerCount = ktx2File.getLayers() || 1; + const levelCount = ktx2File.getLevels(); + const faceCount = ktx2File.getFaces(); + const hasAlpha = ktx2File.getHasAlpha(); + const dfdFlags = ktx2File.getDFDFlags(); + const { transcoderFormat, engineFormat, engineType } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); + if ( ! width || ! height || ! levelCount ) { + cleanup(); + throw new Error( `KTX2Loader: Invalid texture ktx2File:${JSON.stringify(ktx2File)} w:${width} h: ${height} levelCount:${levelCount}` ); + } + + if ( ! ktx2File.startTranscoding() ) { + cleanup(); + throw new Error( 'KTX2Loader: .startTranscoding failed' ); + } + + const faces = []; + const buffers = []; + + for ( let face = 0; face < faceCount; face ++ ) { + const mipmaps = []; + for ( let mip = 0; mip < levelCount; mip ++ ) { + const layerMips = []; + let mipWidth, mipHeight; + for ( let layer = 0; layer < layerCount; layer ++ ) { + const levelInfo = ktx2File.getImageLevelInfo( mip, layer, face ); + if ( face === 0 && mip === 0 && layer === 0 && ( levelInfo.origWidth % 4 !== 0 || levelInfo.origHeight % 4 !== 0 ) ) { + console.warn( 'KTX2Loader: ETC1S and UASTC textures should use multiple-of-four dimensions.' ); + } + + if ( levelCount > 1 ) { + mipWidth = levelInfo.origWidth; + mipHeight = levelInfo.origHeight; + } else { + // Handles non-multiple-of-four dimensions in textures without mipmaps. Textures with + // mipmaps must use multiple-of-four dimensions, for some texture formats and APIs. + // See mrdoob/three.js#25908. + mipWidth = levelInfo.width; + mipHeight = levelInfo.height; + } + + let dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, layer, 0, transcoderFormat ) ); + const status = ktx2File.transcodeImage( dst, mip, layer, face, transcoderFormat, 0, - 1, - 1 ); + + if ( engineType === EngineType.HalfFloatType ) { + dst = new Uint16Array( dst.buffer, dst.byteOffset, dst.byteLength / Uint16Array.BYTES_PER_ELEMENT ); + } + + if ( ! status ) { + cleanup(); + throw new Error( 'KTX2Loader: .transcodeImage failed.' ); + } + layerMips.push( dst ); + } + const mipData = concat( layerMips ); + mipmaps.push( { data: mipData, width: mipWidth, height: mipHeight } ); + buffers.push( mipData.buffer ); + } + faces.push( { mipmaps, width, height, format: engineFormat, type: engineType } ); + } + cleanup(); + return { faces, buffers, width, height, hasAlpha, dfdFlags, format: engineFormat, type: engineType }; + } + // + + // Optimal choice of a transcoder target format depends on the Basis format (ETC1S, UASTC, or + // UASTC HDR), device capabilities, and texture dimensions. The list below ranks the formats + // separately for each format. Currently, priority is assigned based on: + // + // high quality > low quality > uncompressed + // + // Prioritization may be revisited, or exposed for configuration, in the future. + // + // Reference: https://github.com/KhronosGroup/3D-Formats-Guidelines/blob/main/KTXDeveloperGuide.md + const FORMAT_OPTIONS = [ + { + if: 'astcSupported', + basisFormat: [ BasisFormat.UASTC ], + transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ], + engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ], + engineType: [ EngineType.UnsignedByteType ], + priorityETC1S: Infinity, + priorityUASTC: 1, + needsPowerOfTwo: false, + }, + { + if: 'bptcSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC ], + transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ], + engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ], + engineType: [ EngineType.UnsignedByteType ], + priorityETC1S: 3, + priorityUASTC: 2, + needsPowerOfTwo: false, + }, + { + if: 'dxtSupported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC ], + transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ], + engineFormat: [ EngineFormat.RGBA_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ], + engineType: [ EngineType.UnsignedByteType ], + priorityETC1S: 4, + priorityUASTC: 5, + needsPowerOfTwo: false, + }, + { + if: 'etc2Supported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC ], + transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ], + engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ], + engineType: [ EngineType.UnsignedByteType ], + priorityETC1S: 1, + priorityUASTC: 3, + needsPowerOfTwo: false, + }, + { + if: 'etc1Supported', + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC ], + transcoderFormat: [ TranscoderFormat.ETC1 ], + engineFormat: [ EngineFormat.RGB_ETC1_Format ], + engineType: [ EngineType.UnsignedByteType ], + priorityETC1S: 2, + priorityUASTC: 4, + needsPowerOfTwo: false, + }, + + // Uncompressed fallbacks. + + { + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC ], + transcoderFormat: [ TranscoderFormat.RGBA32, TranscoderFormat.RGBA32 ], + engineFormat: [ EngineFormat.RGBAFormat, EngineFormat.RGBAFormat ], + engineType: [ EngineType.UnsignedByteType, EngineType.UnsignedByteType ], + priorityETC1S: 100, + priorityUASTC: 100, + needsPowerOfTwo: false, + }, + { + basisFormat: [ BasisFormat.UASTC_HDR ], + transcoderFormat: [ TranscoderFormat.RGBA_HALF ], + engineFormat: [ EngineFormat.RGBAFormat ], + engineType: [ EngineType.HalfFloatType ], + priorityHDR: 100, + needsPowerOfTwo: false, + } + ]; + + const OPTIONS = { + // TODO: For ETC1S we intentionally sort by _UASTC_ priority, preserving + // a historical accident shown to avoid performance pitfalls for Linux with + // Firefox & AMD GPU (RadeonSI). Further work needed. + // See https://github.com/mrdoob/three.js/pull/29730. + [ BasisFormat.ETC1S ]: FORMAT_OPTIONS + .filter( ( opt ) => opt.basisFormat.includes( BasisFormat.ETC1S ) ) + .sort( ( a, b ) => a.priorityUASTC - b.priorityUASTC ), + + [ BasisFormat.UASTC ]: FORMAT_OPTIONS + .filter( ( opt ) => opt.basisFormat.includes( BasisFormat.UASTC ) ) + .sort( ( a, b ) => a.priorityUASTC - b.priorityUASTC ), + + [ BasisFormat.UASTC_HDR ]: FORMAT_OPTIONS + .filter( ( opt ) => opt.basisFormat.includes( BasisFormat.UASTC_HDR ) ) + .sort( ( a, b ) => a.priorityHDR - b.priorityHDR ), + }; + + function getTranscoderFormat( basisFormat, width, height, hasAlpha ) { + const options = OPTIONS[ basisFormat ]; + for ( let i = 0; i < options.length; i++ ) { + const opt = options[ i ]; + if ( opt.if && ! config[ opt.if ] ) continue; + if ( ! opt.basisFormat.includes( basisFormat ) ) continue; + if ( hasAlpha && opt.transcoderFormat.length < 2 ) continue; + if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue; + const transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ]; + const engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ]; + const engineType = opt.engineType[ 0 ]; + console.log(`opt: ${JSON.stringify(opt)}`); + + return { transcoderFormat, engineFormat, engineType }; + } + throw new Error( 'KTX2Loader: Failed to identify transcoding target.' ); + } + + function isPowerOfTwo( value ) { + if ( value <= 2 ) return true; + return ( value & ( value - 1 ) ) === 0 && value !== 0; + } + + /** Concatenates N byte arrays. */ + function concat( arrays ) { + if ( arrays.length === 1 ) return arrays[ 0 ]; + let totalByteLength = 0; + + for ( let i = 0; i < arrays.length; i ++ ) { + const array = arrays[ i ]; + totalByteLength += array.byteLength; + } + + const result = new Uint8Array( totalByteLength ); + let byteOffset = 0; + + for ( let i = 0; i < arrays.length; i ++ ) { + const array = arrays[ i ]; + result.set( array, byteOffset ); + byteOffset += array.byteLength; + } + + return result; + }"; +} + +@:structInit class BasisWorkerMessage { + public final id : String; + public final type = 'transcode'; + public final data : { + faces : Array<{ mipmaps : Array, width : Int, height : Int, format : Int, type : Int }>, + width : Int, + height : Int, + hasAlpha : Bool, + format : Int, + type : Int, + dfdFlags : Int, + }; + public final error : String = null; +} +#end \ No newline at end of file From 5419565801f5fc8611c397a6048b9595188b1571 Mon Sep 17 00:00:00 2001 From: Leo Bergman Date: Wed, 15 Jan 2025 15:07:41 +0000 Subject: [PATCH 03/23] style: Formatting fix --- hxd/PixelFormat.hx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hxd/PixelFormat.hx b/hxd/PixelFormat.hx index ebbf9438c4..484b53c4d1 100644 --- a/hxd/PixelFormat.hx +++ b/hxd/PixelFormat.hx @@ -23,9 +23,9 @@ enum PixelFormat { RG16U; RGB16U; RGBA16U; - ASTC(v:Int); - ETC(v:Int); - S3TC(v:Int); + ASTC( v:Int ); + ETC( v:Int ); + S3TC( v:Int ); Depth16; Depth24; Depth24Stencil8; From ba459cd98d02a63c11ce3926d63e665dacc6b4f0 Mon Sep 17 00:00:00 2001 From: Leo Bergman Date: Wed, 15 Jan 2025 15:08:44 +0000 Subject: [PATCH 04/23] feat: Adding SDF Metadata for Littera XML Format --- h2d/Font.hx | 8 ++++++++ hxd/fmt/bfnt/FontParser.hx | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/h2d/Font.hx b/h2d/Font.hx index 1949b2d53b..14888c754a 100644 --- a/h2d/Font.hx +++ b/h2d/Font.hx @@ -185,6 +185,14 @@ class Font { Defaults to `hxd.Charset.getDefault()`. **/ public var charset : hxd.Charset; + /** + Distance range used to generate SDF Font and useful for some implementations of SDF Font rendering. + **/ + public var distanceRange:Int; + /** + SDF Field type used when generating. + **/ + public var fieldType:String; var glyphs : Map; var nullChar : FontChar; var defaultChar : FontChar; diff --git a/hxd/fmt/bfnt/FontParser.hx b/hxd/fmt/bfnt/FontParser.hx index 885d5dd572..c77de6c3b1 100644 --- a/hxd/fmt/bfnt/FontParser.hx +++ b/hxd/fmt/bfnt/FontParser.hx @@ -55,6 +55,12 @@ class FontParser { font.lineHeight = Std.parseInt(xml.node.common.att.lineHeight); font.baseLine = Std.parseInt(xml.node.common.att.base); + // This node contains SDF Metadata + if(xml.hasNode.distanceField) { + font.distanceRange = Std.parseInt(xml.node.distanceField.att.distanceRange); + font.fieldType = Std.string(xml.node.distanceField.att.fieldType); + } + for ( p in xml.node.pages.elements ) { if ( p.att.id == "0" ) { resolveTileWithFallback(p.att.file); From 05000f11a11b2221b1c4bd5294d71da115618f16 Mon Sep 17 00:00:00 2001 From: Leo Bergman Date: Wed, 15 Jan 2025 16:14:50 +0000 Subject: [PATCH 05/23] chore: Add misc files for publishing to npm --- .github/workflows/sync_wiki.yml | 32 -------------------------------- .gitignore | 1 + .npmrc | 3 +++ bun.lockb | Bin 0 -> 3276 bytes hxformat.json | 3 +++ package.json | 24 ++++++++++++++++++++++++ 6 files changed, 31 insertions(+), 32 deletions(-) delete mode 100644 .github/workflows/sync_wiki.yml create mode 100644 .npmrc create mode 100755 bun.lockb create mode 100644 hxformat.json create mode 100644 package.json diff --git a/.github/workflows/sync_wiki.yml b/.github/workflows/sync_wiki.yml deleted file mode 100644 index 9e480556c7..0000000000 --- a/.github/workflows/sync_wiki.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Synchronize Wiki - -on: - schedule: - # daily, midnight UTC - - cron: "0 0 * * *" - workflow_dispatch: - -jobs: - synchronize: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - with: - repository: ${{ github.repository }}.wiki - fetch-depth: 0 - - name: Set git credentials - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - name: Fetch - run: | - git remote add upstream ${{ github.server_url }}/${{ github.repository_owner }}/heaps-doc - git fetch upstream master - - name: Merge - run: | - git merge upstream/master --no-edit - - name: Push - run: | - git push diff --git a/.gitignore b/.gitignore index 3d6e16b0fd..6a995472b4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ bin .vscode /tools/meshtools/out *.exe +/node_modules diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..698a75a0dd --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +registry=https://hacksawgaming-281033247142.d.codeartifact.eu-west-1.amazonaws.com/npm/hacksaw-client-artifacts/ +//hacksawgaming-281033247142.d.codeartifact.eu-west-1.amazonaws.com/npm/hacksaw-client-artifacts/:always-auth=true +//hacksawgaming-281033247142.d.codeartifact.eu-west-1.amazonaws.com/npm/hacksaw-client-artifacts/:_authToken=${CODEARTIFACT_AUTH_TOKEN} diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..cfd09658e5383acac748746d48a37f80bf5ceb12 GIT binary patch literal 3276 zcmd5;e@q*76hELaQc)5N4rek8%Qht5U9Uh_26kpy)xdO{ZW`PeJIb{jYp>niwG;+| z{7@XrmIWPV6K5n&GjtGVR5EcU1G2Hq{lOMM2^bZF!5>qXh0LY+9^98zhOyCv>?PNC z-|zSR_`DzYeO-~v`V^Th5+zoU{DvZ*q=$!gdF;C#qD!D19@)*SwBM+Yq$nz}>O$Vw zx%)Q@iAC|bd%pO&V@PZtY4#>ZHK#TteHQ#JCk}*wVrCf?JCE0BYl#TVtP?4!#-Y$c zK$ZDFU(IbJNlFUhdt8H<%4%;ZEL0X_Plz!+kIo- z#m~=H?l{x)TAv}X#&B=-%k-gVv!o4eg^yWUPY>%qs7}b<(0``*lMuTYA0b`s(Z@4m zlMTZo!E1dfPbOHlG-Ot0uY7*ombd5Fk~Czi(5Ex9pyc~gF;GiBND z2T%3*dta;-FB}`Q?z>W3cd5`^HFnHiwtBQVtsulM_6;GO?Zfh5R)cq7y*IPGZLs-` zg!4D|rso&L|LPx^dTy$fom{ou%Vi4Bu2>jr^)dBn-;Pd^$8THTt`$TS$pb~d?2=CPH z{T_|$a}?Rmh?1i6E*C?F4zpDj1gpVhG$>+);Bh=`$T09O(J6TxdJH5uJxlnG{c}~b zrqeDHeU2~PWCf*#)o1P*tRa3Y*w;o)Z*v&>=nrd$dZ>?f@WD0i*AQFU=+ozHH<=53 z76`{Yhljfr4w-9YUd2XmjEUh)6h$T?nRIZ1M@EVwGm*@?$cUX8vnOO~l4%GhX2!^H z6h-DLnU`<^w$R!?pUn58Lq8m2z8zcS9JV7`+=TzV!iwPb3o>I1sDdoHH~m9;FZd2;e@08g7NoH0G!l(B}(Ffql9-BT>^vMM@wyUGYYx=c`1 znqzo3U*VAe9tgNu$?GOoXuC@kB$XyQ3QOwF(sQr$Y~>l%S>cpL`U^g7@mue2Eu7#9&kNHqXP$cn}f5~_yjSg7IP+GEMJ bSE?*h#=`1yh+{#-(8PEzVMOq`-I4Sc`G&&M literal 0 HcmV?d00001 diff --git a/hxformat.json b/hxformat.json new file mode 100644 index 0000000000..29383dacc0 --- /dev/null +++ b/hxformat.json @@ -0,0 +1,3 @@ +{ + "disableFormatting": true +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000000..df41e52430 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "@hacksawstudios/heaps", + "version": "2.0.7", + "description": "_High Performance Game Framework_", + "main": "index.js", + "devDependencies": { + "@hacksawstudios/haxe-module-installer": "1.2.12", + "fs-extra": "11.2.0" + }, + "scripts": { + "postinstall": "npx @hacksawstudios/haxe-module-installer", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/HacksawStudios/heaps.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/HacksawStudios/heaps/issues" + }, + "homepage": "https://github.com/HacksawStudios/heaps#readme" +} \ No newline at end of file From 36b384bd77512fc5397d7a29bac5f8bbb3e307d2 Mon Sep 17 00:00:00 2001 From: Leo Bergman Date: Wed, 15 Jan 2025 16:23:07 +0000 Subject: [PATCH 06/23] feat: Ktx2 support --- h3d/impl/GlDriver.hx | 64 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/h3d/impl/GlDriver.hx b/h3d/impl/GlDriver.hx index d771357b4d..e9fc4463dd 100644 --- a/h3d/impl/GlDriver.hx +++ b/h3d/impl/GlDriver.hx @@ -87,6 +87,15 @@ class GlDriver extends Driver { static var UID = 0; public var gl : GL; public static var ALLOW_WEBGL2 = true; + + public var textureSupport:{ + astc:Bool, + astcHDR:Bool, + etc1:Bool, + etc2:Bool, + dxt:Bool, + bptc:Bool, + }; #end #if (hlsdl||usegl) @@ -992,9 +1001,9 @@ class GlDriver extends Driver { case GL.RGB10_A2: GL.RGBA; case GL.RED, GL.R8, GL.R16F, GL.R32F, 0x822A: GL.RED; case GL.RG, GL.RG8, GL.RG16F, GL.RG32F, 0x822C: GL.RG; - case GL.RGB16F, GL.RGB32F, 0x8054, 0x8E8F: GL.RGB; - case 0x83F1, 0x83F2, 0x83F3, 0x805B, 0x8E8C: GL.RGBA; - default: throw "Invalid format " + t.internalFmt; + case GL.RGB16F, GL.RGB32F, 0x8054, hxd.CompressedTextureFormat.BPTC_FORMAT.RGB_BPTC_UNSIGNED, hxd.CompressedTextureFormat.ETC_FORMAT.RGB_ETC1: GL.RGB; + case 0x805B, hxd.CompressedTextureFormat.DXT_FORMAT.RGBA_DXT1,hxd.CompressedTextureFormat.DXT_FORMAT.RGBA_DXT3, + hxd.CompressedTextureFormat.DXT_FORMAT.RGBA_DXT5,hxd.CompressedTextureFormat.ASTC_FORMAT.RGBA_4x4, hxd.CompressedTextureFormat.BPTC_FORMAT.RGBA_BPTC : GL.RGBA; default: throw "Invalid format " + t.internalFmt; } } @@ -1083,8 +1092,7 @@ class GlDriver extends Driver { tt.internalFmt = GL.R11F_G11F_B10F; tt.pixelFmt = GL.UNSIGNED_INT_10F_11F_11F_REV; case S3TC(n) if( n <= maxCompressedTexturesSupport ): - if( t.width&3 != 0 || t.height&3 != 0 ) - throw "Compressed texture "+t+" has size "+t.width+"x"+t.height+" - must be a multiple of 4"; + checkMult4(t); switch( n ) { case 1: tt.internalFmt = 0x83F1; // COMPRESSED_RGBA_S3TC_DXT1_EXT case 2: tt.internalFmt = 0x83F2; // COMPRESSED_RGBA_S3TC_DXT3_EXT @@ -1093,6 +1101,18 @@ class GlDriver extends Driver { case 7: tt.internalFmt = 0x8E8C; // COMPRESSED_RGBA_BPTC_UNORM default: throw "Unsupported texture format "+t.format; } + case ASTC(n): + checkMult4(t); + switch (n) { + case 10: tt.internalFmt = hxd.CompressedTextureFormat.ASTC_FORMAT.RGBA_4x4; + default: throw "Unsupported texture format " + t.format; + } + case ETC(n): + checkMult4(t); + switch (n) { + case 0: tt.internalFmt = hxd.CompressedTextureFormat.ETC_FORMAT.RGB_ETC1; + case 1: tt.internalFmt = hxd.CompressedTextureFormat.ETC_FORMAT.RGBA_ETC2; + } default: throw "Unsupported texture format "+t.format; } @@ -1169,6 +1189,11 @@ class GlDriver extends Driver { return tt; } + inline function checkMult4( t : h3d.mat.Texture ) { + if( t.width & 3 != 0 || t.height & 3 != 0 ) + throw "Compressed texture " + t + " has size " + t.width + "x" + t.height + " - must be a multiple of 4"; + } + function restoreBind() { var t = boundTextures[lastActiveIndex]; if( t == null ) @@ -1409,7 +1434,7 @@ class GlDriver extends Driver { case RGB10A2, RG11B10UF: new Uint32Array(@:privateAccess pixels.bytes.b.buffer, pixels.offset, dataLen>>2); default: new Uint8Array(@:privateAccess pixels.bytes.b.buffer, pixels.offset, dataLen); } - if( t.format.match(S3TC(_)) ) { + if( t.format.match(S3TC(_) | ASTC(_) | ETC(_)) ) { if( t.flags.has(IsArray) || t.flags.has(Is3D) ) gl.compressedTexSubImage3D(face, mipLevel, 0, 0, side, pixels.width, pixels.height, 1, t.t.internalFmt, buffer); else @@ -1893,20 +1918,37 @@ class GlDriver extends Driver { } #if js + public function checkTextureSupport() { + final checkExtension = ext -> { + gl.getExtension(ext) != null; + } + return { + astc: checkExtension('WEBGL_compressed_texture_astc'), + astcHDR: checkExtension('WEBGL_compressed_texture_astc') + && gl.getExtension('WEBGL_compressed_texture_astc').getSupportedProfiles().includes('hdr'), + etc1: false, // Not supported on WebGL2 (https://registry.khronos.org/OpenGL-Refpages/es3/html/glCompressedTexSubImage2D.xhtml); checkExtension('WEBGL_compressed_texture_etc1'), + etc2: checkExtension('WEBGL_compressed_texture_etc'), + dxt: checkExtension('WEBGL_compressed_texture_s3tc'), + bptc: checkExtension('EXT_texture_compression_bptc'), + } + } + var features : Map = new Map(); var has16Bits : Bool; function makeFeatures() { for( f in Type.allEnums(Feature) ) features.set(f,checkFeature(f)); - if( gl.getExtension("WEBGL_compressed_texture_s3tc") != null ) { - maxCompressedTexturesSupport = 3; - if( gl.getExtension("EXT_texture_compression_bptc") != null ) - maxCompressedTexturesSupport = 7; + textureSupport = checkTextureSupport(); + maxCompressedTexturesSupport = if( textureSupport.dxt || textureSupport.etc1 || textureSupport.etc2 || textureSupport.astc ) { + gl.getExtension("EXT_texture_compression_bptc") != null ? 7 : 3; + } else { + 3; } if( glES < 3 ) gl.getExtension("WEBGL_depth_texture"); has16Bits = gl.getExtension("EXT_texture_norm16") != null; // 16 bit textures } + function checkFeature( f : Feature ) { return switch( f ) { @@ -2126,4 +2168,4 @@ class GlDriver extends Driver { } -#end +#end \ No newline at end of file From 9d5d2a675005299a7763f10069df133ef39ad623 Mon Sep 17 00:00:00 2001 From: Rasmus Brinck Date: Fri, 17 Jan 2025 12:32:49 +0000 Subject: [PATCH 07/23] feat: Add json sdf font parser --- hxd/fmt/bfnt/FontParser.hx | 7 +++ hxd/fmt/bfnt/JsonFontParser.hx | 86 ++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 hxd/fmt/bfnt/JsonFontParser.hx diff --git a/hxd/fmt/bfnt/FontParser.hx b/hxd/fmt/bfnt/FontParser.hx index 885d5dd572..fe4410b146 100644 --- a/hxd/fmt/bfnt/FontParser.hx +++ b/hxd/fmt/bfnt/FontParser.hx @@ -40,6 +40,13 @@ class FontParser { font.baseLine = 0; + // Json Format generated by https://github.com/Chlumsky/msdf-atlas-gen + if(bytes.getString(0,1) == "{") { + resolveTileSameName(); + return hxd.fmt.bfnt.JsonFontParser.parse(bytes,tile); + } + + switch( bytes.getInt32(0) ) { case 0x544E4642: // Internal BFNT return hxd.fmt.bfnt.Reader.parse(bytes, function( tp : String ) { resolveTileWithFallback(tp); return tile; }); diff --git a/hxd/fmt/bfnt/JsonFontParser.hx b/hxd/fmt/bfnt/JsonFontParser.hx new file mode 100644 index 0000000000..d9e6b954dc --- /dev/null +++ b/hxd/fmt/bfnt/JsonFontParser.hx @@ -0,0 +1,86 @@ +package hxd.fmt.bfnt; + +import h2d.Font.FontChar; +import haxe.Json; +using thx.Arrays; + +typedef JsonFontAtlas = { + var type:String; + var distanceRange:Int; + var size:Int; + var width:Int; + var height:Int; +} + +typedef JsonFontMetrics = { + var emSize:Float; + var lineHeight:Float; + var ascender:Float; + var descender:Float; + var underlineY:Float; + var underlineThickness:Float; +} + +typedef JsonFontBounds = { + var left:Float; + var bottom:Float; + var right:Float; + var top:Float; +} + +typedef JsonFontGlyph = { + var unicode:Int; + var advance:Float; + var planeBounds:JsonFontBounds; + var atlasBounds:JsonFontBounds; +} + + +typedef JsonFontFormat = { + var atlas:JsonFontAtlas; + var metrics:JsonFontMetrics; + var glyphs:Array; +} + +@:access(h2d.Font) +class JsonFontParser { + + public static inline function parse(bytes : haxe.io.Bytes,tile) : h2d.Font { + final font = new h2d.Font(null,0); + final glyphs = font.glyphs; + font.tile = tile; + + final data:JsonFontFormat = Json.parse(bytes.getString(0,bytes.length)); + + font.size = data.atlas.size; + font.initSize = font.size; + font.distanceRange = data.atlas.distanceRange; + font.fieldType = data.atlas.type; + + font.lineHeight = data.metrics.lineHeight * data.atlas.size * 0.5; + + for(glyph in data.glyphs) + { + final xadvance = data.atlas.size * glyph.advance; + if(glyph.atlasBounds != null && glyph.planeBounds != null) { + final x = glyph.atlasBounds.left; + final y = data.atlas.height - glyph.atlasBounds.top; + final width = glyph.atlasBounds.right - glyph.atlasBounds.left; + final height = glyph.atlasBounds.top - glyph.atlasBounds.bottom; + + final offsetX = glyph.planeBounds.left * data.atlas.size; + final offsetY = glyph.planeBounds.top * data.atlas.size; + + final subTile = tile.sub(x,y,width,height,offsetX,-offsetY + data.atlas.size); + glyphs.set(glyph.unicode,new FontChar(subTile,xadvance)); + } else { + final subTile = tile.sub(0,0,1,1,0,0); + glyphs.set(glyph.unicode,new FontChar(subTile,xadvance)); + } + + } + + return font; + } + +} \ No newline at end of file From 036e2ebc35bf25faad66dab2b6f04e9df2d29f61 Mon Sep 17 00:00:00 2001 From: Rasmus Brinck Date: Fri, 17 Jan 2025 12:47:20 +0000 Subject: [PATCH 08/23] feat: Introduce FragDepth global for glsl and hlsl --- hxsl/Ast.hx | 1 + hxsl/Cache.hx | 53 ++++++++++++++++++++++++++++--------------------- hxsl/Checker.hx | 18 +++++++++++------ hxsl/GlslOut.hx | 1 + hxsl/HlslOut.hx | 3 ++- 5 files changed, 46 insertions(+), 30 deletions(-) diff --git a/hxsl/Ast.hx b/hxsl/Ast.hx index 6b73fbcc62..aa0423cf6e 100644 --- a/hxsl/Ast.hx +++ b/hxsl/Ast.hx @@ -291,6 +291,7 @@ enum TGlobal { InstanceID; // gl globals FragCoord; + FragDepth; FrontFacing; // bit casting FloatBitsToInt; diff --git a/hxsl/Cache.hx b/hxsl/Cache.hx index 8ccc7d3cdc..06920122e6 100644 --- a/hxsl/Cache.hx +++ b/hxsl/Cache.hx @@ -1,6 +1,6 @@ package hxsl; -using hxsl.Ast; import hxsl.RuntimeShader; +using hxsl.Ast; class BatchInstanceParams { @@ -55,6 +55,11 @@ class SearchMap { class Cache { + #if shader_debug_dump + public static var DEBUG_IDS = false; + public static var TRACE = true; + #end + var linkCache : SearchMap; var linkShaders : Map; var batchShaders : Map; @@ -243,10 +248,12 @@ class Cache { #if shader_debug_dump var shaderId = @:privateAccess RuntimeShader.UID; - #if ( js && !sys ) + var oldTrace = haxe.Log.trace; + #if js if( shaderId == 0 ) js.Syntax.code("window.shaders = [];"); js.Syntax.code("window.shaders[{0}] = '';", shaderId); - var dbg: { writeString: String->Void, close:Void->Void } = { + var dbg: { writeString: String->Void, close:Void->Void } = null; + dbg = { writeString: (str: String) -> { js.Syntax.code("window.shaders[{0}] += {1}", shaderId, str); }, close: () -> {} }; @@ -254,13 +261,13 @@ class Cache { if( shaderId == 0 ) try sys.FileSystem.createDirectory("shaders") catch( e : Dynamic ) {}; var dbg = sys.io.File.write("shaders/"+shaderId+"_dump.c"); #end - var oldTrace = haxe.Log.trace; + haxe.Log.trace = function(msg,?pos) dbg.writeString(haxe.Log.formatOutput(msg,pos)+"\n"); if( dbg != null ) { dbg.writeString("----- DATAS ----\n\n"); for( s in shaderDatas ) { dbg.writeString("\t\t**** " + s.inst.shader.name + (s.p == 0 ? "" : " P="+s.p)+ " *****\n"); - dbg.writeString(Printer.shaderToString(s.inst.shader,Debug.VAR_IDS)+"\n\n"); + dbg.writeString(Printer.shaderToString(s.inst.shader,DEBUG_IDS)+"\n\n"); } } //TRACE = shaderId == 0; @@ -293,17 +300,17 @@ class Cache { checkRec(v); } + #if debug + Printer.check(s,[for( s in shaderDatas ) s.inst.shader]); + #end + #if shader_debug_dump if( dbg != null ) { dbg.writeString("----- LINK ----\n\n"); - dbg.writeString(Printer.shaderToString(s,Debug.VAR_IDS)+"\n\n"); + dbg.writeString(Printer.shaderToString(s,DEBUG_IDS)+"\n\n"); } #end - #if debug - Printer.check(s,[for( s in shaderDatas ) s.inst.shader]); - #end - var prev = s; var splitter = new hxsl.Splitter(); var sl = try splitter.split(s, mode == Batch ) catch( e : Error ) { e.msg += "\n\nin\n\n"+Printer.shaderToString(s); throw e; }; @@ -322,35 +329,35 @@ class Cache { } + #if debug + for( s in sl ) + Printer.check(s,[prev]); + #end + #if shader_debug_dump if( dbg != null ) { dbg.writeString("----- SPLIT ----\n\n"); for( s in sl ) - dbg.writeString(Printer.shaderToString(s, Debug.VAR_IDS) + "\n\n"); + dbg.writeString(Printer.shaderToString(s, DEBUG_IDS) + "\n\n"); } #end - #if debug - for( s in sl ) - Printer.check(s,[prev]); - #end - var prev = sl; var sl = new hxsl.Dce().dce(sl); + #if debug + for( i => s in sl ) + Printer.check(s,[prev[i]]); + #end + #if shader_debug_dump if( dbg != null ) { dbg.writeString("----- DCE ----\n\n"); for( s in sl ) - dbg.writeString(Printer.shaderToString(s, Debug.VAR_IDS) + "\n\n"); + dbg.writeString(Printer.shaderToString(s, DEBUG_IDS) + "\n\n"); } #end - #if debug - for( i => s in sl ) - Printer.check(s,[prev[i]]); - #end - var r = buildRuntimeShader(sl, paramVars); r.mode = mode; @@ -358,7 +365,7 @@ class Cache { if( dbg != null ) { dbg.writeString("----- FLATTEN ----\n\n"); for( s in r.getShaders() ) - dbg.writeString(Printer.shaderToString(s.data, Debug.VAR_IDS) + "\n\n"); + dbg.writeString(Printer.shaderToString(s.data, DEBUG_IDS) + "\n\n"); } #end diff --git a/hxsl/Checker.hx b/hxsl/Checker.hx index d55d5f9a22..f8a6f87c46 100644 --- a/hxsl/Checker.hx +++ b/hxsl/Checker.hx @@ -186,7 +186,7 @@ class Checker { ]; case ImageStore: []; - case VertexID, InstanceID, FragCoord, FrontFacing: + case VertexID, InstanceID, FragCoord, FrontFacing,FragDepth: null; case AtomicAdd: [{ args : [{ name : "buf", type : TBuffer(TInt, SConst(0), RW) },{ name : "index", type : TInt }, { name : "data", type : TInt }], ret : TInt }]; @@ -225,6 +225,7 @@ class Checker { globals.set("vertexID", { t : TInt, g : VertexID }); globals.set("instanceID", { t : TInt, g : InstanceID }); globals.set("fragCoord", { t : vec4, g : FragCoord }); + globals.set("fragDepth", { t : TFloat, g : FragDepth }); globals.set("frontFacing", { t : TBool, g : FrontFacing }); for( gname => vl in gvars ) globals.set(gname, { t : TStruct([ @@ -418,7 +419,13 @@ class Checker { case TArray(e, _): checkWrite(e); return; - default: + case TGlobal(g): + switch(g) { + case FragDepth: + return; + default: + } + default: } error("This expression cannot be assigned", e.p); } @@ -799,12 +806,11 @@ class Checker { checkConst(e); einit = e; } - if( v.type == null ) error("Type required for variable declaration", e.pos); - if( isImport && v.kind == Param ) - continue; - + if( v.type == null ) error("Type required for variable declaration", e.pos); if( vars.exists(v.name) ) error("Duplicate var decl '" + v.name + "'", e.pos); var v = makeVar(v, e.pos); + if( isImport && v.kind == Param ) + continue; switch( v.type ) { case TSampler(T3D, true), TRWTexture(T3D, true, _), TRWTexture(_,_,3): diff --git a/hxsl/GlslOut.hx b/hxsl/GlslOut.hx index daf30e09f5..a2cf628d93 100644 --- a/hxsl/GlslOut.hx +++ b/hxsl/GlslOut.hx @@ -67,6 +67,7 @@ class GlslOut { set(BVec3, "bvec3"); set(BVec4, "bvec4"); set(FragCoord, "gl_FragCoord"); + set(FragDepth, "gl_FragDepth"); set(FrontFacing, "gl_FrontFacing"); set(FloatBitsToUint, "_floatBitsToUint"); set(UintBitsToFloat, "_uintBitsToFloat"); diff --git a/hxsl/HlslOut.hx b/hxsl/HlslOut.hx index ce66cfcb2e..742bedb5eb 100644 --- a/hxsl/HlslOut.hx +++ b/hxsl/HlslOut.hx @@ -77,6 +77,7 @@ class HlslOut { m.set(BVec3, "bool3"); m.set(BVec4, "bool4"); m.set(FragCoord,"_in.__pos__"); + m.set(FragDepth,"SV_Depth"); m.set(FloatBitsToInt, "asint"); m.set(FloatBitsToUint, "asuint"); m.set(IntBitsToFloat, "asfloat"); @@ -458,7 +459,7 @@ class HlslOut { case TCall({ e : TGlobal(g = (Texel)) }, args): addValue(args[0], tabs); add(".Load("); - switch( args[0].t ) { + switch( args[1].t ) { case TSampler(dim,arr): var size = Tools.getDimSize(dim, arr) + 1; add("int"+size+"("); From 0d722c7673e24639094846adc516bed580cce81c Mon Sep 17 00:00:00 2001 From: Rasmus Brinck Date: Fri, 17 Jan 2025 12:49:23 +0000 Subject: [PATCH 09/23] chore: Update codestyle --- hxd/fmt/bfnt/JsonFontParser.hx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hxd/fmt/bfnt/JsonFontParser.hx b/hxd/fmt/bfnt/JsonFontParser.hx index d9e6b954dc..4906e2558e 100644 --- a/hxd/fmt/bfnt/JsonFontParser.hx +++ b/hxd/fmt/bfnt/JsonFontParser.hx @@ -59,10 +59,9 @@ class JsonFontParser { font.lineHeight = data.metrics.lineHeight * data.atlas.size * 0.5; - for(glyph in data.glyphs) - { + for(glyph in data.glyphs) { final xadvance = data.atlas.size * glyph.advance; - if(glyph.atlasBounds != null && glyph.planeBounds != null) { + if( glyph.atlasBounds != null && glyph.planeBounds != null ) { final x = glyph.atlasBounds.left; final y = data.atlas.height - glyph.atlasBounds.top; final width = glyph.atlasBounds.right - glyph.atlasBounds.left; From 6c0f69699431042666d765b5c32714ec1ca75184 Mon Sep 17 00:00:00 2001 From: Rasmus Brinck Date: Fri, 17 Jan 2025 12:54:33 +0000 Subject: [PATCH 10/23] chore: Restore Cache --- hxsl/Cache.hx | 53 ++++++++++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/hxsl/Cache.hx b/hxsl/Cache.hx index 06920122e6..8ccc7d3cdc 100644 --- a/hxsl/Cache.hx +++ b/hxsl/Cache.hx @@ -1,6 +1,6 @@ package hxsl; -import hxsl.RuntimeShader; using hxsl.Ast; +import hxsl.RuntimeShader; class BatchInstanceParams { @@ -55,11 +55,6 @@ class SearchMap { class Cache { - #if shader_debug_dump - public static var DEBUG_IDS = false; - public static var TRACE = true; - #end - var linkCache : SearchMap; var linkShaders : Map; var batchShaders : Map; @@ -248,12 +243,10 @@ class Cache { #if shader_debug_dump var shaderId = @:privateAccess RuntimeShader.UID; - var oldTrace = haxe.Log.trace; - #if js + #if ( js && !sys ) if( shaderId == 0 ) js.Syntax.code("window.shaders = [];"); js.Syntax.code("window.shaders[{0}] = '';", shaderId); - var dbg: { writeString: String->Void, close:Void->Void } = null; - dbg = { + var dbg: { writeString: String->Void, close:Void->Void } = { writeString: (str: String) -> { js.Syntax.code("window.shaders[{0}] += {1}", shaderId, str); }, close: () -> {} }; @@ -261,13 +254,13 @@ class Cache { if( shaderId == 0 ) try sys.FileSystem.createDirectory("shaders") catch( e : Dynamic ) {}; var dbg = sys.io.File.write("shaders/"+shaderId+"_dump.c"); #end - + var oldTrace = haxe.Log.trace; haxe.Log.trace = function(msg,?pos) dbg.writeString(haxe.Log.formatOutput(msg,pos)+"\n"); if( dbg != null ) { dbg.writeString("----- DATAS ----\n\n"); for( s in shaderDatas ) { dbg.writeString("\t\t**** " + s.inst.shader.name + (s.p == 0 ? "" : " P="+s.p)+ " *****\n"); - dbg.writeString(Printer.shaderToString(s.inst.shader,DEBUG_IDS)+"\n\n"); + dbg.writeString(Printer.shaderToString(s.inst.shader,Debug.VAR_IDS)+"\n\n"); } } //TRACE = shaderId == 0; @@ -300,17 +293,17 @@ class Cache { checkRec(v); } - #if debug - Printer.check(s,[for( s in shaderDatas ) s.inst.shader]); - #end - #if shader_debug_dump if( dbg != null ) { dbg.writeString("----- LINK ----\n\n"); - dbg.writeString(Printer.shaderToString(s,DEBUG_IDS)+"\n\n"); + dbg.writeString(Printer.shaderToString(s,Debug.VAR_IDS)+"\n\n"); } #end + #if debug + Printer.check(s,[for( s in shaderDatas ) s.inst.shader]); + #end + var prev = s; var splitter = new hxsl.Splitter(); var sl = try splitter.split(s, mode == Batch ) catch( e : Error ) { e.msg += "\n\nin\n\n"+Printer.shaderToString(s); throw e; }; @@ -329,35 +322,35 @@ class Cache { } - #if debug - for( s in sl ) - Printer.check(s,[prev]); - #end - #if shader_debug_dump if( dbg != null ) { dbg.writeString("----- SPLIT ----\n\n"); for( s in sl ) - dbg.writeString(Printer.shaderToString(s, DEBUG_IDS) + "\n\n"); + dbg.writeString(Printer.shaderToString(s, Debug.VAR_IDS) + "\n\n"); } #end - var prev = sl; - var sl = new hxsl.Dce().dce(sl); - #if debug - for( i => s in sl ) - Printer.check(s,[prev[i]]); + for( s in sl ) + Printer.check(s,[prev]); #end + var prev = sl; + var sl = new hxsl.Dce().dce(sl); + #if shader_debug_dump if( dbg != null ) { dbg.writeString("----- DCE ----\n\n"); for( s in sl ) - dbg.writeString(Printer.shaderToString(s, DEBUG_IDS) + "\n\n"); + dbg.writeString(Printer.shaderToString(s, Debug.VAR_IDS) + "\n\n"); } #end + #if debug + for( i => s in sl ) + Printer.check(s,[prev[i]]); + #end + var r = buildRuntimeShader(sl, paramVars); r.mode = mode; @@ -365,7 +358,7 @@ class Cache { if( dbg != null ) { dbg.writeString("----- FLATTEN ----\n\n"); for( s in r.getShaders() ) - dbg.writeString(Printer.shaderToString(s.data, DEBUG_IDS) + "\n\n"); + dbg.writeString(Printer.shaderToString(s.data, Debug.VAR_IDS) + "\n\n"); } #end From ac24f56dd627dad60ad3ce2c1407e82204da2d76 Mon Sep 17 00:00:00 2001 From: Rasmus Brinck Date: Sat, 18 Jan 2025 12:40:23 +0000 Subject: [PATCH 11/23] fix: Remove some revert changes and apply special case for fragdepth --- hxsl/Cache.hx | 53 ++++++++++++++++++++++++++++--------------------- hxsl/Checker.hx | 12 +++-------- hxsl/HlslOut.hx | 2 +- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/hxsl/Cache.hx b/hxsl/Cache.hx index 8ccc7d3cdc..06920122e6 100644 --- a/hxsl/Cache.hx +++ b/hxsl/Cache.hx @@ -1,6 +1,6 @@ package hxsl; -using hxsl.Ast; import hxsl.RuntimeShader; +using hxsl.Ast; class BatchInstanceParams { @@ -55,6 +55,11 @@ class SearchMap { class Cache { + #if shader_debug_dump + public static var DEBUG_IDS = false; + public static var TRACE = true; + #end + var linkCache : SearchMap; var linkShaders : Map; var batchShaders : Map; @@ -243,10 +248,12 @@ class Cache { #if shader_debug_dump var shaderId = @:privateAccess RuntimeShader.UID; - #if ( js && !sys ) + var oldTrace = haxe.Log.trace; + #if js if( shaderId == 0 ) js.Syntax.code("window.shaders = [];"); js.Syntax.code("window.shaders[{0}] = '';", shaderId); - var dbg: { writeString: String->Void, close:Void->Void } = { + var dbg: { writeString: String->Void, close:Void->Void } = null; + dbg = { writeString: (str: String) -> { js.Syntax.code("window.shaders[{0}] += {1}", shaderId, str); }, close: () -> {} }; @@ -254,13 +261,13 @@ class Cache { if( shaderId == 0 ) try sys.FileSystem.createDirectory("shaders") catch( e : Dynamic ) {}; var dbg = sys.io.File.write("shaders/"+shaderId+"_dump.c"); #end - var oldTrace = haxe.Log.trace; + haxe.Log.trace = function(msg,?pos) dbg.writeString(haxe.Log.formatOutput(msg,pos)+"\n"); if( dbg != null ) { dbg.writeString("----- DATAS ----\n\n"); for( s in shaderDatas ) { dbg.writeString("\t\t**** " + s.inst.shader.name + (s.p == 0 ? "" : " P="+s.p)+ " *****\n"); - dbg.writeString(Printer.shaderToString(s.inst.shader,Debug.VAR_IDS)+"\n\n"); + dbg.writeString(Printer.shaderToString(s.inst.shader,DEBUG_IDS)+"\n\n"); } } //TRACE = shaderId == 0; @@ -293,17 +300,17 @@ class Cache { checkRec(v); } + #if debug + Printer.check(s,[for( s in shaderDatas ) s.inst.shader]); + #end + #if shader_debug_dump if( dbg != null ) { dbg.writeString("----- LINK ----\n\n"); - dbg.writeString(Printer.shaderToString(s,Debug.VAR_IDS)+"\n\n"); + dbg.writeString(Printer.shaderToString(s,DEBUG_IDS)+"\n\n"); } #end - #if debug - Printer.check(s,[for( s in shaderDatas ) s.inst.shader]); - #end - var prev = s; var splitter = new hxsl.Splitter(); var sl = try splitter.split(s, mode == Batch ) catch( e : Error ) { e.msg += "\n\nin\n\n"+Printer.shaderToString(s); throw e; }; @@ -322,35 +329,35 @@ class Cache { } + #if debug + for( s in sl ) + Printer.check(s,[prev]); + #end + #if shader_debug_dump if( dbg != null ) { dbg.writeString("----- SPLIT ----\n\n"); for( s in sl ) - dbg.writeString(Printer.shaderToString(s, Debug.VAR_IDS) + "\n\n"); + dbg.writeString(Printer.shaderToString(s, DEBUG_IDS) + "\n\n"); } #end - #if debug - for( s in sl ) - Printer.check(s,[prev]); - #end - var prev = sl; var sl = new hxsl.Dce().dce(sl); + #if debug + for( i => s in sl ) + Printer.check(s,[prev[i]]); + #end + #if shader_debug_dump if( dbg != null ) { dbg.writeString("----- DCE ----\n\n"); for( s in sl ) - dbg.writeString(Printer.shaderToString(s, Debug.VAR_IDS) + "\n\n"); + dbg.writeString(Printer.shaderToString(s, DEBUG_IDS) + "\n\n"); } #end - #if debug - for( i => s in sl ) - Printer.check(s,[prev[i]]); - #end - var r = buildRuntimeShader(sl, paramVars); r.mode = mode; @@ -358,7 +365,7 @@ class Cache { if( dbg != null ) { dbg.writeString("----- FLATTEN ----\n\n"); for( s in r.getShaders() ) - dbg.writeString(Printer.shaderToString(s.data, Debug.VAR_IDS) + "\n\n"); + dbg.writeString(Printer.shaderToString(s.data, DEBUG_IDS) + "\n\n"); } #end diff --git a/hxsl/Checker.hx b/hxsl/Checker.hx index f8a6f87c46..8ab89382bc 100644 --- a/hxsl/Checker.hx +++ b/hxsl/Checker.hx @@ -419,13 +419,9 @@ class Checker { case TArray(e, _): checkWrite(e); return; - case TGlobal(g): - switch(g) { - case FragDepth: - return; - default: - } - default: + case TGlobal(FragDepth): + return; + default: } error("This expression cannot be assigned", e.p); } @@ -809,8 +805,6 @@ class Checker { if( v.type == null ) error("Type required for variable declaration", e.pos); if( vars.exists(v.name) ) error("Duplicate var decl '" + v.name + "'", e.pos); var v = makeVar(v, e.pos); - if( isImport && v.kind == Param ) - continue; switch( v.type ) { case TSampler(T3D, true), TRWTexture(T3D, true, _), TRWTexture(_,_,3): diff --git a/hxsl/HlslOut.hx b/hxsl/HlslOut.hx index 742bedb5eb..701e9d260d 100644 --- a/hxsl/HlslOut.hx +++ b/hxsl/HlslOut.hx @@ -459,7 +459,7 @@ class HlslOut { case TCall({ e : TGlobal(g = (Texel)) }, args): addValue(args[0], tabs); add(".Load("); - switch( args[1].t ) { + switch( args[0].t ) { case TSampler(dim,arr): var size = Tools.getDimSize(dim, arr) + 1; add("int"+size+"("); From 9ec179a05a9846c278e1e6b73ecb2f1182a699b0 Mon Sep 17 00:00:00 2001 From: Rasmus Brinck Date: Sat, 18 Jan 2025 12:41:59 +0000 Subject: [PATCH 12/23] fix: Restore broken code --- hxsl/Cache.hx | 53 ++++++++++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/hxsl/Cache.hx b/hxsl/Cache.hx index 06920122e6..8ccc7d3cdc 100644 --- a/hxsl/Cache.hx +++ b/hxsl/Cache.hx @@ -1,6 +1,6 @@ package hxsl; -import hxsl.RuntimeShader; using hxsl.Ast; +import hxsl.RuntimeShader; class BatchInstanceParams { @@ -55,11 +55,6 @@ class SearchMap { class Cache { - #if shader_debug_dump - public static var DEBUG_IDS = false; - public static var TRACE = true; - #end - var linkCache : SearchMap; var linkShaders : Map; var batchShaders : Map; @@ -248,12 +243,10 @@ class Cache { #if shader_debug_dump var shaderId = @:privateAccess RuntimeShader.UID; - var oldTrace = haxe.Log.trace; - #if js + #if ( js && !sys ) if( shaderId == 0 ) js.Syntax.code("window.shaders = [];"); js.Syntax.code("window.shaders[{0}] = '';", shaderId); - var dbg: { writeString: String->Void, close:Void->Void } = null; - dbg = { + var dbg: { writeString: String->Void, close:Void->Void } = { writeString: (str: String) -> { js.Syntax.code("window.shaders[{0}] += {1}", shaderId, str); }, close: () -> {} }; @@ -261,13 +254,13 @@ class Cache { if( shaderId == 0 ) try sys.FileSystem.createDirectory("shaders") catch( e : Dynamic ) {}; var dbg = sys.io.File.write("shaders/"+shaderId+"_dump.c"); #end - + var oldTrace = haxe.Log.trace; haxe.Log.trace = function(msg,?pos) dbg.writeString(haxe.Log.formatOutput(msg,pos)+"\n"); if( dbg != null ) { dbg.writeString("----- DATAS ----\n\n"); for( s in shaderDatas ) { dbg.writeString("\t\t**** " + s.inst.shader.name + (s.p == 0 ? "" : " P="+s.p)+ " *****\n"); - dbg.writeString(Printer.shaderToString(s.inst.shader,DEBUG_IDS)+"\n\n"); + dbg.writeString(Printer.shaderToString(s.inst.shader,Debug.VAR_IDS)+"\n\n"); } } //TRACE = shaderId == 0; @@ -300,17 +293,17 @@ class Cache { checkRec(v); } - #if debug - Printer.check(s,[for( s in shaderDatas ) s.inst.shader]); - #end - #if shader_debug_dump if( dbg != null ) { dbg.writeString("----- LINK ----\n\n"); - dbg.writeString(Printer.shaderToString(s,DEBUG_IDS)+"\n\n"); + dbg.writeString(Printer.shaderToString(s,Debug.VAR_IDS)+"\n\n"); } #end + #if debug + Printer.check(s,[for( s in shaderDatas ) s.inst.shader]); + #end + var prev = s; var splitter = new hxsl.Splitter(); var sl = try splitter.split(s, mode == Batch ) catch( e : Error ) { e.msg += "\n\nin\n\n"+Printer.shaderToString(s); throw e; }; @@ -329,35 +322,35 @@ class Cache { } - #if debug - for( s in sl ) - Printer.check(s,[prev]); - #end - #if shader_debug_dump if( dbg != null ) { dbg.writeString("----- SPLIT ----\n\n"); for( s in sl ) - dbg.writeString(Printer.shaderToString(s, DEBUG_IDS) + "\n\n"); + dbg.writeString(Printer.shaderToString(s, Debug.VAR_IDS) + "\n\n"); } #end - var prev = sl; - var sl = new hxsl.Dce().dce(sl); - #if debug - for( i => s in sl ) - Printer.check(s,[prev[i]]); + for( s in sl ) + Printer.check(s,[prev]); #end + var prev = sl; + var sl = new hxsl.Dce().dce(sl); + #if shader_debug_dump if( dbg != null ) { dbg.writeString("----- DCE ----\n\n"); for( s in sl ) - dbg.writeString(Printer.shaderToString(s, DEBUG_IDS) + "\n\n"); + dbg.writeString(Printer.shaderToString(s, Debug.VAR_IDS) + "\n\n"); } #end + #if debug + for( i => s in sl ) + Printer.check(s,[prev[i]]); + #end + var r = buildRuntimeShader(sl, paramVars); r.mode = mode; @@ -365,7 +358,7 @@ class Cache { if( dbg != null ) { dbg.writeString("----- FLATTEN ----\n\n"); for( s in r.getShaders() ) - dbg.writeString(Printer.shaderToString(s.data, DEBUG_IDS) + "\n\n"); + dbg.writeString(Printer.shaderToString(s.data, Debug.VAR_IDS) + "\n\n"); } #end From abc4d10c3c38acda16d9904498530849979aea75 Mon Sep 17 00:00:00 2001 From: Rasmus Brinck Date: Sat, 18 Jan 2025 12:43:16 +0000 Subject: [PATCH 13/23] fix: More code reversion --- hxsl/Checker.hx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hxsl/Checker.hx b/hxsl/Checker.hx index 8ab89382bc..9bd9c2b8db 100644 --- a/hxsl/Checker.hx +++ b/hxsl/Checker.hx @@ -802,7 +802,10 @@ class Checker { checkConst(e); einit = e; } - if( v.type == null ) error("Type required for variable declaration", e.pos); + if( v.type == null ) error("Type required for variable declaration", e.pos); + if( isImport && v.kind == Param ) + continue; + if( vars.exists(v.name) ) error("Duplicate var decl '" + v.name + "'", e.pos); var v = makeVar(v, e.pos); From 95dfcffbd643d17a455c1010cbc5047ddaa97b61 Mon Sep 17 00:00:00 2001 From: Rasmus Brinck Date: Tue, 21 Jan 2025 12:55:04 +0000 Subject: [PATCH 14/23] Prevent linker from opt out fragDepth --- hxsl/Ast.hx | 2 ++ hxsl/Linker.hx | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/hxsl/Ast.hx b/hxsl/Ast.hx index aa0423cf6e..d77f49dca2 100644 --- a/hxsl/Ast.hx +++ b/hxsl/Ast.hx @@ -523,6 +523,8 @@ class Tools { return hasSideEffect(it) || hasSideEffect(loop); case TArray(e, index): return hasSideEffect(e) || hasSideEffect(index); + case TGlobal(FragDepth): + return true; case TConst(_), TVar(_), TGlobal(_): return false; case TCall({ e : TGlobal(SetLayout) },_): diff --git a/hxsl/Linker.hx b/hxsl/Linker.hx index b16c608f66..e813a637fc 100644 --- a/hxsl/Linker.hx +++ b/hxsl/Linker.hx @@ -31,6 +31,7 @@ private class ShaderInfos { public var vertex : Null; public var onStack : Bool; public var hasDiscard : Bool; + public var hasFragDepth : Bool; public var isCompute : Bool; public var marked : Null; public function new(n, v) { @@ -201,6 +202,11 @@ class Linker { return { e : TVar(v.v), t : v.v.type, p : e.p }; case TBinop(op, e1, e2): switch( [op, e1.e] ) { + case [OpAssign, TGlobal(FragDepth)]: + if( curShader != null ) { + curShader.hasFragDepth = true; + } + return { e : TBinop(op, { e : TGlobal(FragDepth),t : TFloat, p : e.p }, e2), t : e.t, p : e.p }; case [OpAssign, TVar(v)] if( !locals.exists(v.id) ): var e2 = mapExprVar(e2); var v = allocVar(v, e1.p); @@ -414,7 +420,7 @@ class Linker { // force shaders containing discard to be included for( s in shaders ) - if( s.hasDiscard || s.isCompute ) { + if( s.hasDiscard || s.isCompute || s.hasFragDepth) { initDependencies(s); entry.deps.set(s, true); } From 21008892891ed79f329d5c6317a07a7000466791 Mon Sep 17 00:00:00 2001 From: Rasmus Brinck Date: Tue, 21 Jan 2025 13:00:21 +0000 Subject: [PATCH 15/23] Restore cache file --- hxsl/Cache.hx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hxsl/Cache.hx b/hxsl/Cache.hx index 8ccc7d3cdc..8c87e7c76e 100644 --- a/hxsl/Cache.hx +++ b/hxsl/Cache.hx @@ -243,11 +243,11 @@ class Cache { #if shader_debug_dump var shaderId = @:privateAccess RuntimeShader.UID; - #if ( js && !sys ) - if( shaderId == 0 ) js.Syntax.code("window.shaders = [];"); - js.Syntax.code("window.shaders[{0}] = '';", shaderId); + #if ( js && !sys && !hxnodejs ) + if( shaderId == 0 ) js.Syntax.code("window.shaders_debug_dump = [];"); + js.Syntax.code("window.shaders_debug_dump[{0}] = '';", shaderId); var dbg: { writeString: String->Void, close:Void->Void } = { - writeString: (str: String) -> { js.Syntax.code("window.shaders[{0}] += {1}", shaderId, str); }, + writeString: (str: String) -> { js.Syntax.code("window.shaders_debug_dump[{0}] += {1}", shaderId, str); }, close: () -> {} }; #else From 71834707fb943740214751deb266740566fee32e Mon Sep 17 00:00:00 2001 From: Rasmus Brinck Date: Tue, 21 Jan 2025 13:03:39 +0000 Subject: [PATCH 16/23] another restore --- hxsl/Cache.hx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hxsl/Cache.hx b/hxsl/Cache.hx index 8c87e7c76e..63e3551ab9 100644 --- a/hxsl/Cache.hx +++ b/hxsl/Cache.hx @@ -243,9 +243,9 @@ class Cache { #if shader_debug_dump var shaderId = @:privateAccess RuntimeShader.UID; - #if ( js && !sys && !hxnodejs ) - if( shaderId == 0 ) js.Syntax.code("window.shaders_debug_dump = [];"); - js.Syntax.code("window.shaders_debug_dump[{0}] = '';", shaderId); + #if ( js && !sys ) + if( shaderId == 0 ) js.Syntax.code("window.shaders = [];"); + js.Syntax.code("window.shaders[{0}] = '';", shaderId); var dbg: { writeString: String->Void, close:Void->Void } = { writeString: (str: String) -> { js.Syntax.code("window.shaders_debug_dump[{0}] += {1}", shaderId, str); }, close: () -> {} From c2336efbfabbc72cde981be923a19a791338de6c Mon Sep 17 00:00:00 2001 From: Rasmus Brinck Date: Tue, 21 Jan 2025 13:04:07 +0000 Subject: [PATCH 17/23] and another restore --- hxsl/Cache.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hxsl/Cache.hx b/hxsl/Cache.hx index 63e3551ab9..8ccc7d3cdc 100644 --- a/hxsl/Cache.hx +++ b/hxsl/Cache.hx @@ -247,7 +247,7 @@ class Cache { if( shaderId == 0 ) js.Syntax.code("window.shaders = [];"); js.Syntax.code("window.shaders[{0}] = '';", shaderId); var dbg: { writeString: String->Void, close:Void->Void } = { - writeString: (str: String) -> { js.Syntax.code("window.shaders_debug_dump[{0}] += {1}", shaderId, str); }, + writeString: (str: String) -> { js.Syntax.code("window.shaders[{0}] += {1}", shaderId, str); }, close: () -> {} }; #else From 7d9b83ad0200dd8cd460e0b058f3ea2b2d7baea3 Mon Sep 17 00:00:00 2001 From: Rasmus Brinck Date: Thu, 23 Jan 2025 10:47:17 +0000 Subject: [PATCH 18/23] Adding read dependency --- hxsl/Linker.hx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/hxsl/Linker.hx b/hxsl/Linker.hx index e813a637fc..92e700a2aa 100644 --- a/hxsl/Linker.hx +++ b/hxsl/Linker.hx @@ -202,10 +202,22 @@ class Linker { return { e : TVar(v.v), t : v.v.type, p : e.p }; case TBinop(op, e1, e2): switch( [op, e1.e] ) { - case [OpAssign, TGlobal(FragDepth)]: + case [OpAssign | OpAssignOp(_), TGlobal(FragDepth)]: if( curShader != null ) { curShader.hasFragDepth = true; } + + var e2 = mapExprVar(e2); + switch(e2.e) { + case TVar(v2): + var v2 = allocVar(v2,e2.p); + if( !curShader.readMap.exists(v2.id) ) { + curShader.readMap.set(v2.id, v2); + curShader.readVars.push(v2); + } + default: + } + return { e : TBinop(op, { e : TGlobal(FragDepth),t : TFloat, p : e.p }, e2), t : e.t, p : e.p }; case [OpAssign, TVar(v)] if( !locals.exists(v.id) ): var e2 = mapExprVar(e2); From 48ea6e03751ad0ed0b674f683e9f65a2516a6b4f Mon Sep 17 00:00:00 2001 From: Rasmus Brinck Date: Tue, 28 Jan 2025 15:45:46 +0000 Subject: [PATCH 19/23] DCE for fragdepth --- hxsl/Dce.hx | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/hxsl/Dce.hx b/hxsl/Dce.hx index 3e85dbd989..5eb24f3a60 100644 --- a/hxsl/Dce.hx +++ b/hxsl/Dce.hx @@ -62,6 +62,7 @@ class Dce { var used : Map; var channelVars : Array; var markAsKeep : Bool; + var fragDepthId = Tools.allocVarId(); public function new() { } @@ -202,6 +203,29 @@ class Dce { link(v, writeTo); case TSwiz({ e : TVar(v) }, swiz): link(v, writeTo, swizBits(swiz)); + case TBinop(OpAssign | OpAssignOp(_), { e : TGlobal(FragDepth) }, e2 ): + var v:TVar = { + id: fragDepthId, + name: "FragDepth", + type: TFloat, + kind: Global, + }; + + var v = get(v); + v.keep = 15; + + switch(e.e) { + // Last assign will always clear all other dependencies + case TBinop(OpAssign,_,_): + v.adeps = []; + v.deps.clear(); + default: + } + + writeTo.push(v, 15); + check(e2, writeTo, isAffected); + writeTo.pop(); + case TBinop(OpAssign | OpAssignOp(_), { e : TVar(v) }, e): var v = get(v); writeTo.push(v,15); @@ -296,6 +320,8 @@ class Dce { count++; } return { e : TBlock(out), p : e.p, t : e.t }; + case TBinop(OpAssign | OpAssignOp(_), {e: TGlobal(FragDepth) },{e: TVar(v) }) if(get(v).used == 0): + return { e : TConst(CNull), t : e.t, p : e.p }; case TVarDecl(v,_) | TBinop(OpAssign | OpAssignOp(_), { e : (TVar(v) | TSwiz( { e : TVar(v) }, _) | TArray( { e : TVar(v) }, _)) }, _) if( get(v).used == 0 ): return { e : TConst(CNull), t : e.t, p : e.p }; case TBinop(OpAssign | OpAssignOp(_), { e : TSwiz( { e : TVar(v) }, swiz) }, _) if( get(v).used & swizBits(swiz) == 0 ): From f3a85f7d77f39ac027eb49563ad573328c60ceed Mon Sep 17 00:00:00 2001 From: Rasmus Brinck Date: Tue, 28 Jan 2025 15:56:28 +0000 Subject: [PATCH 20/23] Handle write only --- hxsl/Dce.hx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/hxsl/Dce.hx b/hxsl/Dce.hx index 5eb24f3a60..18c3876560 100644 --- a/hxsl/Dce.hx +++ b/hxsl/Dce.hx @@ -203,25 +203,28 @@ class Dce { link(v, writeTo); case TSwiz({ e : TVar(v) }, swiz): link(v, writeTo, swizBits(swiz)); - case TBinop(OpAssign | OpAssignOp(_), { e : TGlobal(FragDepth) }, e2 ): + case TBinop(op, { e : TGlobal(FragDepth) }, e2 ): var v:TVar = { id: fragDepthId, name: "FragDepth", type: TFloat, kind: Global, }; - var v = get(v); - v.keep = 15; - - switch(e.e) { + + switch(op) { // Last assign will always clear all other dependencies - case TBinop(OpAssign,_,_): + case OpAssign: v.adeps = []; v.deps.clear(); + case OpAssignOp(_): default: + // FragDepth is writeonly + return; } + v.keep = 15; + writeTo.push(v, 15); check(e2, writeTo, isAffected); writeTo.pop(); From e55cdccac724f135bd30fd14cf3ea13262ddb4cc Mon Sep 17 00:00:00 2001 From: Rasmus Brinck Date: Tue, 28 Jan 2025 16:00:19 +0000 Subject: [PATCH 21/23] code cleanup --- hxsl/Dce.hx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/hxsl/Dce.hx b/hxsl/Dce.hx index 18c3876560..382e81d6c0 100644 --- a/hxsl/Dce.hx +++ b/hxsl/Dce.hx @@ -211,7 +211,6 @@ class Dce { kind: Global, }; var v = get(v); - switch(op) { // Last assign will always clear all other dependencies case OpAssign: @@ -219,16 +218,12 @@ class Dce { v.deps.clear(); case OpAssignOp(_): default: - // FragDepth is writeonly return; } - v.keep = 15; - writeTo.push(v, 15); check(e2, writeTo, isAffected); writeTo.pop(); - case TBinop(OpAssign | OpAssignOp(_), { e : TVar(v) }, e): var v = get(v); writeTo.push(v,15); From f736aa8462fe0a54cf41e5b2fb02d08dd9a55e10 Mon Sep 17 00:00:00 2001 From: Leo Bergman Date: Mon, 3 Feb 2025 09:25:04 +0000 Subject: [PATCH 22/23] fix: Avoid conditionals in shader --- h3d/shader/SignedDistanceField.hx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/h3d/shader/SignedDistanceField.hx b/h3d/shader/SignedDistanceField.hx index 7e101dcf00..ffbc8f1ed2 100644 --- a/h3d/shader/SignedDistanceField.hx +++ b/h3d/shader/SignedDistanceField.hx @@ -29,13 +29,15 @@ class SignedDistanceField extends hxsl.Shader { function fragment() { var textureSample : Vec4 = textureColor; - var distance : Float; - - distance = if (channel == 0) textureSample.r; - else if (channel == 1) textureSample.g; - else if (channel == 2) textureSample.b; - else if (channel == 3) textureSample.a; - else median(textureSample.r, textureSample.g, textureSample.b); + var distances = [ + textureSample.r, + textureSample.g, + textureSample.b, + textureSample.a, + median(textureSample.r, textureSample.g, textureSample.b), + ]; + var ch = int(clamp(float(channel),0,4)); + var distance = distances[ch]; var smoothVal = autoSmoothing ? abs(fwidth(distance) * 0.5) : smoothing; textureColor = vec4(1.0, 1.0, 1.0, smoothstep(alphaCutoff - smoothVal, alphaCutoff + smoothVal, distance)); From b3fb037f99516a1e13b78755664b1b0417f70771 Mon Sep 17 00:00:00 2001 From: Rasmus Brinck Date: Thu, 6 Feb 2025 08:53:31 +0000 Subject: [PATCH 23/23] Remove DX try for FragDepth --- hxsl/HlslOut.hx | 1 - 1 file changed, 1 deletion(-) diff --git a/hxsl/HlslOut.hx b/hxsl/HlslOut.hx index 701e9d260d..ce66cfcb2e 100644 --- a/hxsl/HlslOut.hx +++ b/hxsl/HlslOut.hx @@ -77,7 +77,6 @@ class HlslOut { m.set(BVec3, "bool3"); m.set(BVec4, "bool4"); m.set(FragCoord,"_in.__pos__"); - m.set(FragDepth,"SV_Depth"); m.set(FloatBitsToInt, "asint"); m.set(FloatBitsToUint, "asuint"); m.set(IntBitsToFloat, "asfloat");