{"id":1871,"date":"2026-09-17T12:17:03","date_gmt":"2026-09-17T20:17:03","guid":{"rendered":"https:\/\/www.ludicon.com\/castano\/blog\/?p=1871"},"modified":"2026-09-18T10:19:12","modified_gmt":"2026-09-18T18:19:12","slug":"spark-1-4","status":"publish","type":"post","link":"https:\/\/www.ludicon.com\/castano\/blog\/2026\/09\/spark-1-4\/","title":{"rendered":"Spark 1.4"},"content":{"rendered":"\n<p>I&#8217;m excited to announce the release of <em>Spark<\/em> 1.4.<\/p>\n\n\n\n<p><em><a href=\"https:\/\/ludicon.com\/spark\/\" data-type=\"link\" data-id=\"https:\/\/ludicon.com\/spark\/\">Spark<\/a><\/em> is a set of real-time GPU texture codecs that produce results competitive with offline encoders while running thousands of times faster, on every GPU from budget phones to high-end PCs and consoles.<\/p>\n\n\n\n<p>The highlight of this release is HDR. <em>Spark<\/em> 1.4 adds two new HDR codecs: <strong>BC6H<\/strong> for desktop and consoles and <strong>ASTC HDR<\/strong> for mobile, and both deliver quality that matches or exceeds the best offline encoders at real-time speeds.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-style-default\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"630\" src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/light_probe_hangar_interior_bc6h_q2.png\" alt=\"\" class=\"wp-image-1895\" srcset=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/light_probe_hangar_interior_bc6h_q2.png 1200w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/light_probe_hangar_interior_bc6h_q2-267x140.png 267w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/light_probe_hangar_interior_bc6h_q2-700x368.png 700w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/light_probe_hangar_interior_bc6h_q2-768x403.png 768w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/light_probe_hangar_interior_bc6h_q2-800x420.png 800w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><\/figure>\n\n\n\n<p>Real-time HDR compression opens up a new range of applications:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Light probes and reflection maps<\/strong> that are re-rendered as the scene changes and compressed on the fly.<\/li>\n\n\n\n<li><strong>Dynamic lightmaps<\/strong> for time-of-day cycles and destructible environments.<\/li>\n\n\n\n<li><strong>Dynamic skies or any other HDR render target<\/strong> whose contents can be cached across frames.<\/li>\n\n\n\n<li><strong>HDR images<\/strong> decoded at runtime, from EXR, AVIF or JPEG\/R, without the need for an offline pipeline.<\/li>\n<\/ul>\n\n\n\n<p><strong>What&#8217;s new in Spark 1.4:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>New HDR codecs<\/strong>: <a href=\"#bc6h\">BC6H<\/a> and <a href=\"#astc-hdr\">ASTC HDR<\/a>. With offline quality at real-time performance.<\/li>\n\n\n\n<li><strong>BC1 Ultra<\/strong>: a new <a href=\"#bc1-ultra\">quality level for BC1<\/a> that surpasses the perceptual quality of offline encoders at a fraction of their cost.<\/li>\n\n\n\n<li><strong>BC7 NRM<\/strong>: a new <a href=\"#bc7-nrm\">BC7 codec for packed material maps<\/a> that store normals, roughness and metalness in a single texture.<\/li>\n\n\n\n<li><strong>ASTC 5\u00d75<\/strong>: a new <a href=\"#astc-5x5\">block size<\/a> that fills the gap between our 4\u00d74 and 6\u00d76 codecs.<\/li>\n\n\n\n<li><strong>Broader compatibility<\/strong>: <a href=\"#broader-compatibility\">PowerVR Series D<\/a> (Pixel 10), <a href=\"#vulkan-1-0\">Vulkan 1.0 devices<\/a>, improved <a href=\"#gles-mediump\">GLES <code>mediump<\/code> support<\/a>, and improved <a href=\"#ps5\">PlayStation\u00ae 5 support<\/a>.<\/li>\n<\/ul>\n\n\n\n<p>This release also includes a new Getting Started guide and Performance Guidelines, many new examples, improved regression testing, bug fixes, and many other small quality and performance improvements.<\/p>\n\n\n\n<p>It&#8217;s exciting to see <em>Spark<\/em> gaining momentum and shipping in a wide range of games and applications. Check out our <a href=\"https:\/\/ludicon.com#clients\" target=\"_blank\" rel=\"noreferrer noopener\">front page<\/a> for the latest additions to our client list.<\/p>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"wp-block-heading\">HDR Codecs<\/h2>\n\n\n\n<p>Until now, <em>Spark<\/em> handled HDR content through our RGBM codecs, which pack an HDR range into an LDR format. RGBM works everywhere, but it requires a decode in the shader and its range is limited. The two new codecs target the native HDR formats instead: the compressed texture is sampled directly and decodes to 16-bit floats, so there&#8217;s no color transform and no shader changes on the sampling side.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"bc6h\">BC6H<\/h3>\n\n\n\n<p>Our previous codecs deliberately target a subset of their format: the modes and partitions that give the best quality per cycle within a real-time budget. The new BC6H codec breaks from that strategy. It is our first codec that can target the <em>entire<\/em> format: all 14 modes and all 32 partition shapes.<\/p>\n\n\n\n<p>Which subset of the format the codec targets is configured at compile time, and we provide four presets. At the lowest level, the codec saturates memory bandwidth just like our other real-time codecs. At the highest level, it produces results that challenge the best offline encoders while running orders of magnitude faster.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1320\" height=\"796\" src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc6-ssimu2.png.png\" alt=\"\" class=\"wp-image-1873\" srcset=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc6-ssimu2.png.png 1320w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc6-ssimu2.png-267x161.png 267w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc6-ssimu2.png-700x422.png 700w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc6-ssimu2.png-768x463.png 768w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc6-ssimu2.png-800x482.png 800w\" sizes=\"auto, (max-width: 1320px) 100vw, 1320px\" \/><\/figure>\n\n\n\n<p>This chart shows the <a href=\"https:\/\/github.com\/cloudinary\/ssimulacra2\" target=\"_blank\" rel=\"noreferrer noopener\">SSIMULACRA2<\/a> score of each encoder against its throughput on a M4 MacBook Pro, over a set of 27 HDR light probes, lightmaps and photographs. Larger scores are better. <em>Spark<\/em> at its highest quality level scores above every other encoder, and nearly as high as OodleTex, while running 400\u00d7 faster. <\/p>\n\n\n\n<p>Compared to the existing real-time BC6H encoders, the picture is even clearer:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1374\" height=\"796\" src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc6-rt-ssimu2.png\" alt=\"\" class=\"wp-image-1875\" srcset=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc6-rt-ssimu2.png 1374w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc6-rt-ssimu2-267x155.png 267w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc6-rt-ssimu2-700x406.png 700w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc6-rt-ssimu2-768x445.png 768w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc6-rt-ssimu2-800x463.png 800w\" sizes=\"auto, (max-width: 1374px) 100vw, 1374px\" \/><\/figure>\n\n\n\n<p>What I find most remarkable is that these results are achieved with a single candidate encoding. There&#8217;s no error evaluation and no comparison between multiple candidate encodings: the codec chooses a mode and a partition up front and never looks back. This is what enables these codecs to run well on the GPU.<\/p>\n\n\n\n<p>Compared with <a href=\"https:\/\/github.com\/knarkowicz\/GPURealTimeBC6H\" data-type=\"link\" data-id=\"https:\/\/github.com\/knarkowicz\/GPURealTimeBC6H\">Krzysztof Narkowicz\u2019s codec<\/a>, <em>Spark<\/em> delivers comparable performance while avoiding some of its worst-case artifacts, as the following image comparisons show.<\/p>\n\n\n\n<div class=\"cmp-tabs\" data-cmp-tabs>\n  <div class=\"cmp-tabbar\" role=\"tablist\" aria-label=\"Texture comparison\">\n    <button class=\"cmp-tab is-active\" role=\"tab\" aria-selected=\"true\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/sevilla03_spark_bc6h_rgb_q1_pair.png\" \n      data-label=\"88.262\">\n        Spark Q1\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/sevilla03_spark_bc6h_rgb_q2_pair.png\" \n      data-label=\"90.255\">\n        Spark Q2\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/sevilla03_knarcovicz_bc6h_rgb_q1_pair.png\" \n      data-label=\"70.281\">\n        Narkowicz Q1\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/sevilla03_knarcovicz_bc6h_rgb_q2_pair.png\" \n      data-label=\"77.483\">\n        Narkowicz Q2\n    <\/button>\n  <\/div>\n\n  <div class=\"cmp-image-wrapper\" style=\"max-width: 768px; margin: 0 auto;\">\n    <img decoding=\"async\" class=\"cmp-img\"\n         src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/sevilla03_spark_bc6h_rgb_q1_pair.png\"\n         alt=\"sevilla03\">\n    <div class=\"cmp-label\">88.262<\/div>\n  <\/div>\n<\/div>\n\n\n\n<div class=\"cmp-tabs\" data-cmp-tabs>\n  <div class=\"cmp-tabbar\" role=\"tablist\" aria-label=\"Texture comparison\">\n    <button class=\"cmp-tab is-active\" role=\"tab\" aria-selected=\"true\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/lightsaber_spark_bc6h_rgb_q1_pair.png\" \n      data-label=\"88.262\">\n        Spark Q1\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/lightsaber_spark_bc6h_rgb_q2_pair.png\" \n      data-label=\"90.255\">\n        Spark Q2\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/lightsaber_knarcovicz_bc6h_rgb_q1_pair.png\" \n      data-label=\"70.281\">\n        Narkowicz Q1\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/lightsaber_knarcovicz_bc6h_rgb_q2_pair.png\" \n      data-label=\"77.483\">\n        Narkowicz Q2\n    <\/button>\n  <\/div>\n\n  <div class=\"cmp-image-wrapper\" style=\"max-width: 768px; margin: 0 auto;\">\n    <img decoding=\"async\" class=\"cmp-img\"\n         src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/lightsaber_spark_bc6h_rgb_q1_pair.png\"\n         alt=\"lightsaber\">\n    <div class=\"cmp-label\">88.262<\/div>\n  <\/div>\n<\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"astc-hdr\">ASTC HDR<\/h3>\n\n\n\n<p>One of the surprises of this release is the new ASTC HDR codec. I did not plan to include it, but the initial results were so promising that I could not stop myself. This first version is intentionally simple: it uses all the HDR endpoint encoding modes, but a single partition and a single weight and endpoint bit distribution.<\/p>\n\n\n\n<p>With these constraints I did not expect great results, but it turns out that the algorithms I developed for BC6H also work exceptionally well with ASTC, and even this first version matches or exceeds the perceptual quality of the existing offline encoders.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1374\" height=\"796\" src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/astc_hdr-offline-ssimu2.png\" alt=\"\" class=\"wp-image-1876\" srcset=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/astc_hdr-offline-ssimu2.png 1374w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/astc_hdr-offline-ssimu2-267x155.png 267w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/astc_hdr-offline-ssimu2-700x406.png 700w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/astc_hdr-offline-ssimu2-768x445.png 768w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/astc_hdr-offline-ssimu2-800x463.png 800w\" sizes=\"auto, (max-width: 1374px) 100vw, 1374px\" \/><\/figure>\n\n\n\n<p>At its highest quality level, <em>Spark<\/em> scores above Arm&#8217;s astcenc at its most exhaustive setting and above every UASTC HDR preset except the slowest, while encoding on the GPU thousands of times faster than these CPU encoders on the same machine. The medium quality level matches astcenc&#8217;s at the default setting.<\/p>\n\n\n\n<p>The existing ASTC HDR encoders minimize the mean squared error in the linearized logarithmic space of the format. As a result, they often spend bits on features that are not perceptually noticeable while under-allocating elsewhere, and that results in errors that are visible with the naked eye. <em>Spark<\/em> is optimized for perceptual metrics instead:<\/p>\n\n\n\n<div class=\"cmp-tabs\" data-cmp-tabs>\n  <div class=\"cmp-tabbar\" role=\"tablist\" aria-label=\"Texture comparison\">\n    <button class=\"cmp-tab is-active\" role=\"tab\" aria-selected=\"true\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/matas_spark_astc_hdr_rgb_q2_pair.png\" \n      data-label=\"86.620\">\n        Spark\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/matas_arm_astc_hdr_rgb_q4_pair.png\" \n      data-label=\"80.456\">\n        Arm\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/matas_basis_astc_hdr_rgb_q4_pair.png\" \n      data-label=\"85.293\">\n        Basis\n    <\/button>\n  <\/div>\n\n  <div class=\"cmp-image-wrapper\" style=\"max-width: 768px; margin: 0 auto;\">\n    <img decoding=\"async\" class=\"cmp-img\"\n         src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/matas_spark_astc_hdr_rgb_q2_pair.png\"\n         alt=\"matas\">\n    <div class=\"cmp-label\">86.620<\/div>\n  <\/div>\n<\/div>\n\n\n\n<div class=\"cmp-tabs\" data-cmp-tabs>\n  <div class=\"cmp-tabbar\" role=\"tablist\" aria-label=\"Texture comparison\">\n    <button class=\"cmp-tab is-active\" role=\"tab\" aria-selected=\"true\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/sevilla02_spark_astc_hdr_rgb_q2_pair.png\" \n      data-label=\"86.620\">\n        Spark\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/sevilla02_arm_astc_hdr_rgb_q4_pair.png\" \n      data-label=\"80.456\">\n        Arm\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/sevilla02_basis_astc_hdr_rgb_q4_pair.png\" \n      data-label=\"85.293\">\n        Basis\n    <\/button>\n  <\/div>\n\n  <div class=\"cmp-image-wrapper\" style=\"max-width: 768px; margin: 0 auto;\">\n    <img decoding=\"async\" class=\"cmp-img\"\n         src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/sevilla02_spark_astc_hdr_rgb_q2_pair.png\"\n         alt=\"matas\">\n    <div class=\"cmp-label\">86.620<\/div>\n  <\/div>\n<\/div>\n\n\n\n<div class=\"cmp-tabs\" data-cmp-tabs>\n  <div class=\"cmp-tabbar\" role=\"tablist\" aria-label=\"Texture comparison\">\n    <button class=\"cmp-tab is-active\" role=\"tab\" aria-selected=\"true\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/chinese_garden_01_spark_astc_hdr_rgb_q2_pair.png\" \n      data-label=\"86.620\">\n        Spark\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/chinese_garden_01_arm_astc_hdr_rgb_q4_pair.png\" \n      data-label=\"80.456\">\n        Arm\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/chinese_garden_01_basis_astc_hdr_rgb_q4_pair.png\" \n      data-label=\"85.293\">\n        Basis\n    <\/button>\n  <\/div>\n\n  <div class=\"cmp-image-wrapper\" style=\"max-width: 768px; margin: 0 auto;\">\n    <img decoding=\"async\" class=\"cmp-img\"\n         src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/chinese_garden_01_spark_astc_hdr_rgb_q2_pair.png\"\n         alt=\"matas\">\n    <div class=\"cmp-label\">86.620<\/div>\n  <\/div>\n<\/div>\n\n\n\n<div class=\"cmp-tabs\" data-cmp-tabs>\n  <div class=\"cmp-tabbar\" role=\"tablist\" aria-label=\"Texture comparison\">\n    <button class=\"cmp-tab is-active\" role=\"tab\" aria-selected=\"true\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/industrial01_spark_astc_hdr_rgb_q2_pair.png\" \n      data-label=\"86.620\">\n        Spark\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/industrial01_arm_astc_hdr_rgb_q4_pair.png\" \n      data-label=\"80.456\">\n        Arm\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/industrial01_basis_astc_hdr_rgb_q4_pair.png\" \n      data-label=\"85.293\">\n        Basis\n    <\/button>\n  <\/div>\n\n  <div class=\"cmp-image-wrapper\" style=\"max-width: 768px; margin: 0 auto;\">\n    <img decoding=\"async\" class=\"cmp-img\"\n         src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/industrial01_spark_astc_hdr_rgb_q2_pair.png\"\n         alt=\"matas\">\n    <div class=\"cmp-label\">86.620<\/div>\n  <\/div>\n<\/div>\n\n\n\n<p>As far as I know this is the only real-time ASTC HDR encoder publicly available, so there&#8217;s nothing else to compare it against on mobile. Here are the three quality levels on a Pixel 8 (Mali-G715):<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1374\" height=\"796\" src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/pixel8-astc-hdr-ssimu2.png\" alt=\"\" class=\"wp-image-1877\" srcset=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/pixel8-astc-hdr-ssimu2.png 1374w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/pixel8-astc-hdr-ssimu2-267x155.png 267w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/pixel8-astc-hdr-ssimu2-700x406.png 700w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/pixel8-astc-hdr-ssimu2-768x445.png 768w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/pixel8-astc-hdr-ssimu2-800x463.png 800w\" sizes=\"auto, (max-width: 1374px) 100vw, 1374px\" \/><\/figure>\n\n\n\n<p>A 1024\u00d7512 light probe compresses in 0.5 ms at the highest quality level, and 0.17 ms at the lowest.<\/p>\n\n\n\n<p>Like all of our codecs, I&#8217;ve taken special care to ensure the code is fully vectorized for 16-bit arithmetic, and that the resulting code also performs well on Adreno. Even though Adreno does not support packed math instructions, in practice the vectorized variants result in less register shuffling, increased occupancy, and a reduced instruction footprint, so the same shader works well on every mobile GPU.<\/p>\n\n\n\n<p>Some mobile devices support both ASTC HDR and BC6H. Our BC6H codec was developed with PC and consoles in mind, but at the lower quality levels it also runs great on mobile. The following chart shows both codecs on an Adreno 740:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1374\" height=\"796\" src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/hdr-codecs-a740.png\" alt=\"\" class=\"wp-image-1878\" srcset=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/hdr-codecs-a740.png 1374w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/hdr-codecs-a740-267x155.png 267w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/hdr-codecs-a740-700x406.png 700w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/hdr-codecs-a740-768x445.png 768w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/hdr-codecs-a740-800x463.png 800w\" sizes=\"auto, (max-width: 1374px) 100vw, 1374px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"bc1-ultra\">BC1 Ultra<\/h2>\n\n\n\n<p>BC1 is the oldest block compression format we support and the one most encoders have had the most time to mature, so I did not expect much headroom. The new <em>Ultra<\/em> quality level proved me wrong. Modern techniques combined with perceptual quality optimizations result in higher quality than the existing offline encoders, and it still runs orders of magnitude faster than them.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1374\" height=\"796\" src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc1-encoders-ssimu2-3.png\" alt=\"\" class=\"wp-image-1879\" srcset=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc1-encoders-ssimu2-3.png 1374w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc1-encoders-ssimu2-3-267x155.png 267w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc1-encoders-ssimu2-3-700x406.png 700w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc1-encoders-ssimu2-3-768x445.png 768w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc1-encoders-ssimu2-3-800x463.png 800w\" sizes=\"auto, (max-width: 1374px) 100vw, 1374px\" \/><\/figure>\n\n\n\n<p>Three things make the difference: the use of the 3-color mode for blocks with near-black texels, a novel endpoint quantization procedure that takes into account the residual quadric of the least squares solver, and a greedy endpoint refinement.<\/p>\n\n\n\n<p>At this quality level it is very difficult to see any difference. The results are nearly indistinguishable from the offline codecs.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"bc7-nrm\">BC7 NRM<\/h2>\n\n\n\n<p>Our existing BC7 codecs are optimized for color images, but BC7 textures are often used to store other material attributes. One of the most common arrangements is to pack two components of the normal in the RG channels and roughness and metalness in the BA channels.<\/p>\n\n\n\n<p>Our color codecs were doing a reasonable job on this kind of data, but it was clear we could do better by tailoring the coding choices to it. Our new codec is optimized for the output of typical shading models, with special attention to smooth normals on glossy surfaces, where a small error in the normal becomes artifacts in the specular highlight.<\/p>\n\n\n\n<p>This codec has mostly been tuned with client assets that I&#8217;m not able to share publicly, but at least I can share some numerical results. The chart below measures the quality of the reconstructed normals with SSIM, which correlates much better with the appearance of the shaded surface than the raw error.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1438\" height=\"512\" src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc7_nrm-normal-ssim-1.png\" alt=\"\" class=\"wp-image-1880\" srcset=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc7_nrm-normal-ssim-1.png 1438w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc7_nrm-normal-ssim-1-267x95.png 267w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc7_nrm-normal-ssim-1-700x249.png 700w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc7_nrm-normal-ssim-1-768x273.png 768w, https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/bc7_nrm-normal-ssim-1-800x285.png 800w\" sizes=\"auto, (max-width: 1438px) 100vw, 1438px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"astc-5x5\">ASTC 5\u00d75<\/h2>\n\n\n\n<p>Targeting large block sizes in GPU encoders is difficult due to the high register pressure. <em>Spark<\/em> 1.2 introduced our first streaming codec with support for ASTC 6\u00d76 blocks. This codec processes one texel at a time, so it does not need to keep all the block colors in registers at the same time. This release refines that strategy and extends it to the 5\u00d75 block size, offering a compression ratio in between the 4\u00d74 and 6\u00d76 codecs and giving developers a finer-grained quality and size tradeoff.<\/p>\n\n\n\n<div class=\"cmp-tabs\" data-cmp-tabs>\n  <div class=\"cmp-tabbar\" role=\"tablist\" aria-label=\"Texture comparison\">\n    <button class=\"cmp-tab is-active\" role=\"tab\" aria-selected=\"true\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/kodim23_astc_rgb_q2_pair.png\" \n      data-label=\"ASTC 4x4\">\n        ASTC 4&#215;4\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/kodim23_astc_5x5_rgb_q2_pair.png\" \n      data-label=\"ASTC 5x5\">\n        ASTC 5&#215;5\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/kodim23_astc_6x6_rgb_q2_pair.png\" \n      data-label=\"ASTC 6x6\">\n        ASTC 6&#215;6\n    <\/button>\n  <\/div>\n\n  <div class=\"cmp-image-wrapper\" style=\"max-width: 768px; margin: 0 auto;\">\n    <img decoding=\"async\" class=\"cmp-img\"\n         src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/kodim23_astc_rgb_q2_pair.png\"\n         alt=\"ASTC 4x4, 5x5 and 6x6\">\n    <div class=\"cmp-label\">ASTC 4&#215;4<\/div>\n  <\/div>\n<\/div>\n\n\n\n<div class=\"cmp-tabs\" data-cmp-tabs>\n  <div class=\"cmp-tabbar\" role=\"tablist\" aria-label=\"Texture comparison\">\n    <button class=\"cmp-tab is-active\" role=\"tab\" aria-selected=\"true\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/grass2_astc_rgb_q2_pair.png\" \n      data-label=\"ASTC 4x4\">\n        ASTC 4&#215;4\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/grass2_astc_5x5_rgb_q2_pair.png\" \n      data-label=\"ASTC 5x5\">\n        ASTC 5&#215;5\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/grass2_astc_6x6_rgb_q2_pair.png\" \n      data-label=\"ASTC 6x6\">\n        ASTC 6&#215;6\n    <\/button>\n  <\/div>\n\n  <div class=\"cmp-image-wrapper\" style=\"max-width: 768px; margin: 0 auto;\">\n    <img decoding=\"async\" class=\"cmp-img\"\n         src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/grass2_astc_rgb_q2_pair.png\"\n         alt=\"ASTC 4x4, 5x5 and 6x6\">\n    <div class=\"cmp-label\">ASTC 4&#215;4<\/div>\n  <\/div>\n<\/div>\n\n\n\n<div class=\"cmp-tabs\" data-cmp-tabs>\n  <div class=\"cmp-tabbar\" role=\"tablist\" aria-label=\"Texture comparison\">\n    <button class=\"cmp-tab is-active\" role=\"tab\" aria-selected=\"true\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/paint_astc_rgb_q2_pair.png\" \n      data-label=\"ASTC 4x4\">\n        ASTC 4&#215;4\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/paint_astc_5x5_rgb_q2_pair.png\" \n      data-label=\"ASTC 5x5\">\n        ASTC 5&#215;5\n    <\/button>\n    <button class=\"cmp-tab\" role=\"tab\" aria-selected=\"false\" \n      data-src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/paint_astc_6x6_rgb_q2_pair.png\" \n      data-label=\"ASTC 6x6\">\n        ASTC 6&#215;6\n    <\/button>\n  <\/div>\n\n  <div class=\"cmp-image-wrapper\" style=\"max-width: 768px; margin: 0 auto;\">\n    <img decoding=\"async\" class=\"cmp-img\"\n         src=\"https:\/\/www.ludicon.com\/castano\/blog\/wp-content\/uploads\/2026\/09\/paint_astc_rgb_q2_pair.png\"\n         alt=\"ASTC 4x4, 5x5 and 6x6\">\n    <div class=\"cmp-label\">ASTC 4&#215;4<\/div>\n  <\/div>\n<\/div>\n\n\n\n<p>Larger blocks reduce bandwidth, but not necessarily the memory footprint. See our previous investigation for the details: <a href=\"https:\/\/www.ludicon.com\/castano\/blog\/2026\/04\/the-true-size-of-astc-textures\/\" target=\"_blank\" rel=\"noreferrer noopener\">The True Size of ASTC Textures<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"broader-compatibility\">Broader Compatibility<\/h2>\n\n\n\n<p>We continue expanding compatibility in both directions: to new devices as they come to market, such as the Pixel 10 and 11, and to legacy devices, such as Vulkan 1.0 hardware, that we had previously thought non-viable.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">PowerVR Series D<\/h3>\n\n\n\n<p>I&#8217;m excited to see PowerVR making a comeback in Android phones with the Series D GPUs in the Pixel 10. I&#8217;m happy to report that <em>Spark<\/em> runs great on this new hardware, and that some outstanding issues from previous devices appear to be fixed and no longer require cumbersome workarounds. Series D is also the first PowerVR generation with ASTC HDR support, so the new codec runs there too.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"vulkan-1-0\">Vulkan 1.0<\/h3>\n\n\n\n<p>This release adds support for many Vulkan 1.0 devices that I had previously written off as non-viable. This includes many early Arm devices, such as the Galaxy S8, as well as some Adrenos, such as the Adreno 618 in the Pixel 3a. In the past, bugs unrelated to the codecs themselves had prevented my examples, demos and benchmarks from running on these devices, and I had assumed Vulkan 1.0 was simply too unstable to be practical. On closer inspection, it turned out to be possible to work around these issues and prove that the codecs run fine on these devices.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"gles-mediump\">Improved GLES <code>mediump<\/code> support<\/h3>\n\n\n\n<p>Improving <code>mediump<\/code> support on GLES mostly came down to tracking down and fixing bugs in the <code>spirv-opt<\/code> and <code>spirv-cross<\/code> toolchains, and those fixes have been contributed upstream (<a href=\"https:\/\/github.com\/KhronosGroup\/SPIRV-Tools\/pull\/6690\" target=\"_blank\" rel=\"noreferrer noopener\">SPIRV-Tools #6690<\/a> , <a href=\"https:\/\/github.com\/KhronosGroup\/SPIRV-Cross\/pull\/2633\" target=\"_blank\" rel=\"noreferrer noopener\">SPIRV-Cross #2633<\/a>).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"ps5\">PlayStation\u00ae 5<\/h3>\n\n\n\n<p>The PlayStation\u00ae 5 codecs have been through extensive testing and a round of optimizations, and the SDK now includes a PS5 example and integration guidelines with performance results for every codec.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Final Words<\/h2>\n\n\n\n<p>Earlier this year I released <a href=\"https:\/\/www.ludicon.com\/castano\/blog\/2026\/06\/spark-for-unity\/\" target=\"_blank\" rel=\"noreferrer noopener\"><em>Spark for Unity<\/em><\/a>, which brings the codecs to the Unity engine, and <a href=\"https:\/\/github.com\/ludicon\/spark.js\" target=\"_blank\" rel=\"noreferrer noopener\">spark.js<\/a> brings them to WebGL and WebGPU. Both will pick up the new codecs in a follow-up release, and the Android and iOS demos will be updated shortly.<\/p>\n\n\n\n<p>If you want the longer story behind this work, I recently appeared on the <a href=\"https:\/\/www.youtube.com\/watch?v=RgMbrszzpN8\" target=\"_blank\" rel=\"noreferrer noopener\">Wookash Podcast<\/a>, where we talked about texture compression, <em>Spark<\/em>, and much more.<\/p>\n\n\n\n<p>If you&#8217;d like to try the new codecs in your own engine, or for licensing inquiries and anything else, reach out at <a href=\"mailto:spark@ludicon.com\" target=\"_blank\" rel=\"noreferrer noopener\">spark@ludicon.com<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;m excited to announce the release of Spark 1.4. Spark is a set of real-time GPU texture codecs that produce results competitive with offline encoders while running thousands of times faster, on every GPU from budget phones to high-end PCs and consoles. The highlight of this release is HDR. Spark 1.4 adds two new HDR&#8230;<\/p>\n","protected":false},"author":1,"featured_media":1895,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9,22],"tags":[],"class_list":["post-1871","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-coding","category-spark"],"_links":{"self":[{"href":"https:\/\/www.ludicon.com\/castano\/blog\/wp-json\/wp\/v2\/posts\/1871","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ludicon.com\/castano\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ludicon.com\/castano\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ludicon.com\/castano\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ludicon.com\/castano\/blog\/wp-json\/wp\/v2\/comments?post=1871"}],"version-history":[{"count":33,"href":"https:\/\/www.ludicon.com\/castano\/blog\/wp-json\/wp\/v2\/posts\/1871\/revisions"}],"predecessor-version":[{"id":1960,"href":"https:\/\/www.ludicon.com\/castano\/blog\/wp-json\/wp\/v2\/posts\/1871\/revisions\/1960"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.ludicon.com\/castano\/blog\/wp-json\/wp\/v2\/media\/1895"}],"wp:attachment":[{"href":"https:\/\/www.ludicon.com\/castano\/blog\/wp-json\/wp\/v2\/media?parent=1871"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ludicon.com\/castano\/blog\/wp-json\/wp\/v2\/categories?post=1871"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ludicon.com\/castano\/blog\/wp-json\/wp\/v2\/tags?post=1871"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}