Skip to content

Commit 59adfc8

Browse files
committed
add(/wiki/palette-calculator): seems to be mostly stable; SHIP IT!
1 parent cbea27c commit 59adfc8

File tree

2 files changed

+207
-0
lines changed

2 files changed

+207
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
+++
2+
title = "Palette Compression Calculator"
3+
description = "A memory calculator for the palette compression technique."
4+
[taxonomies]
5+
categories = []
6+
tags = ["tool"]
7+
[extra]
8+
breakpoints = ""
9+
container = "container-md"
10+
+++
11+
12+
{% warn_notice() %}This page may be *slightly* broken.{% end %}
13+
{{palette_calc(id="calculator")}}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
<div id='{{id}}' class='notice info' style='display:none'>
2+
<div style="text-align:center"><b>Palette Compression</b> Calculator</div>
3+
<div style="display:flex;gap:0.75rem;justify-content:center;align-items:center;padding:0.5rem">
4+
<label>
5+
<span>Palette Entry Size</span><br/>
6+
<input required
7+
type=number
8+
min=1 max=256
9+
step=1 value=4
10+
name="palette-entry-size"
11+
style="text-align:end;width:9em"
12+
/><br/>
13+
<small>In bytes.</small>
14+
</label>
15+
<label>
16+
<span>Maximum Entropy</span><br/>
17+
<input required
18+
type=number
19+
min=1 max=65536
20+
step=1 value=16
21+
name="palette-entropy"
22+
style="text-align:end;width:9em"
23+
/><br/>
24+
<small># of unique states.</small>
25+
</label>
26+
<label>
27+
<span>Cell/Word Size</span><br/>
28+
<select required
29+
name="cell-size"
30+
style="text-align:end;width:9em"
31+
>
32+
<option value=8>u8</option>
33+
<option value=16>u16</option>
34+
<option value=32>u32</option>
35+
<option value=64 selected>u64</option>
36+
<option value=128>u128</option>
37+
<option value=256>u256</option>
38+
</select><br/>
39+
<small>Backing Storage Type</small>
40+
</label>
41+
</div>
42+
<blockquote style="display:flex;justify-content:center;align-items:center;padding:0.5rem">
43+
Palette Memory = PEntrySize + Entropy<br/>
44+
Cell Count = Volume / Voxels Per Cell<br/>
45+
Index Memory = Cell Count * Voxels Per Cell * CellSize / 8
46+
</blockquote>
47+
<table style="margin:0 auto">
48+
<thead></thead>
49+
<tbody></tbody>
50+
</table>
51+
<style>[id="{{id}}"] {
52+
table {border-collapse: collapse}
53+
table td {white-space: nowrap;padding:0.125em 0.5em;text-align:end;border-top:1px solid lightgray}
54+
table td:nth-child(5) {border-right:1px solid lightgray}
55+
span.unit {opacity: 0.75;font-size:0.75em}
56+
}</style>
57+
</div>
58+
<script async defer style='display:none'>
59+
(function() {
60+
// Yes, this code is sinfully ugly.
61+
// No, I will not use a framework.
62+
// No, I give not a singular fuck.
63+
64+
function formatMemory(bytes) {
65+
const K = 1024;
66+
const kilobytes = bytes / K,
67+
megabytes = kilobytes / K,
68+
gigabytes = megabytes / K,
69+
terabytes = gigabytes / K,
70+
petabytes = terabytes / K;
71+
var unit = 'B', measure = Math.ceil(bytes), fixed=false;
72+
if(terabytes >= K) {measure = petabytes; unit = 'PiB'; fixed=true}
73+
if(gigabytes >= K) {measure = terabytes; unit = 'TiB'; fixed=true}
74+
if(megabytes >= K) {measure = gigabytes; unit = 'GiB'; fixed=true}
75+
if(kilobytes >= K) {measure = megabytes; unit = 'MiB'; fixed=true}
76+
if(bytes >= K) {measure = kilobytes; unit = 'KiB'; fixed=true}
77+
return `<span class=measure>${fixed?measure.toFixed(1):measure}</span>&hairsp;<span class=unit>${unit}</span>`;
78+
}
79+
80+
var calculator = document.getElementById('{{id}}');
81+
var calc_input_es = calculator.querySelector('input[name=palette-entry-size]');
82+
var calc_input_me = calculator.querySelector('input[name=palette-entropy]');
83+
var calc_input_cs = calculator.querySelector('select[name=cell-size]');
84+
var calc_thead = calculator.querySelector('thead');
85+
var calc_tbody = calculator.querySelector('tbody');
86+
87+
function update() {
88+
var EntrySize = null, MaxEntropy = null, CellSize = 64;
89+
try {
90+
EntrySize = Number.parseInt(calc_input_es.value || "1");
91+
MaxEntropy = Number.parseInt(calc_input_me.value || "1");
92+
CellSize = calc_input_cs.value;
93+
if (!EntrySize) throw "invalid";
94+
if (!MaxEntropy) throw "invalid";
95+
} catch(e) {
96+
calc_tbody.innerHTML = '';
97+
return;
98+
}
99+
100+
var sizes = [4, 8, 16, 32, 64];
101+
var scols = sizes.map((size) => {
102+
return {
103+
size: size,
104+
area: size * size,
105+
cube: size * size * size,
106+
raw: size * size * size * EntrySize
107+
};
108+
});
109+
110+
var thead = `<tr>`
111+
+ `<th style="writing-mode:vertical-rl;transform:scale(-1);vertical-align:top"
112+
title="Number of unique voxel states in the volume."
113+
>Entropy</th>`
114+
+ `<th style="writing-mode:vertical-rl;transform:scale(-1);vertical-align:top"
115+
title="The number of bits per voxel sample."
116+
>
117+
Bits per Voxel
118+
</th>`
119+
+ `<th style="writing-mode:vertical-rl;transform:scale(-1);vertical-align:top"
120+
title="The number of voxels per cell."
121+
>
122+
Voxels per Cell
123+
</th>`
124+
+ `<th style="writing-mode:vertical-rl;transform:scale(-1);vertical-align:top"
125+
title="The wasted bits per cell."
126+
>
127+
Waste per Cell
128+
</th>`
129+
+ `<th style="writing-mode:vertical-rl;transform:scale(-1);vertical-align:top"
130+
title="The size of the palette, in bytes."
131+
>Palette Memory</th>`
132+
;
133+
for(var scol of scols) {
134+
thead += `<th style="vertical-align:bottom">${scol.size}³<br/><small>${scol.cube}</small></th>`;
135+
}
136+
calc_thead.innerHTML = thead + "</tr>";
137+
138+
var tbody = "";
139+
for(var entropy = 1; entropy <= MaxEntropy; entropy++) {
140+
var entropy_bits = Math.ceil(Math.log2(entropy));
141+
var entries = EntrySize * entropy;
142+
var voxels_per_cell = Math.floor(entropy_bits != 0 ? CellSize / entropy_bits : 0);
143+
var wasted_per_cell = entropy_bits != 0 ? CellSize % entropy_bits : 0;
144+
var packed = wasted_per_cell == 0;
145+
146+
if(entropy_bits != 0 && voxels_per_cell <= 0) {
147+
continue;
148+
}
149+
150+
tbody += `<tr class="${packed?'packed':'wasted'}">`;
151+
tbody += `<td>${entropy}</td>`;
152+
tbody += `<td>${entropy_bits}</td>`;
153+
tbody += `<td>${voxels_per_cell}</td>`;
154+
tbody += `<td ${packed?'':'style="color:red"'}>${wasted_per_cell}</td>`;
155+
tbody += `<td>${formatMemory(entries)}</td>`;
156+
157+
for(var scol of scols) {
158+
var cells = voxels_per_cell == 0 ? 0 : scol.cube / voxels_per_cell;
159+
var wasted = wasted_per_cell == 0 ? 0 : (cells * wasted_per_cell)/8;
160+
var indices = cells * CellSize / 8;
161+
var percent = indices != 0 ? (indices / scol.raw) : 0;
162+
163+
tbody += `<td title="(${cells} / ${voxels_per_cell}) * ${CellSize}/8">`;
164+
tbody += `${formatMemory(indices)}`;
165+
tbody += `<div>${(percent*100).toFixed(1)}<span class=unit>%</span></div>`;
166+
if(wasted != 0)
167+
tbody += `<div style="color:red">${formatMemory(wasted)}</div>`;
168+
tbody += `</td>`;
169+
}
170+
171+
tbody += "</tr>";
172+
}
173+
174+
tbody += `<tr>`;
175+
tbody += `<td>${EntrySize*8}</td>`;
176+
tbody += `<td>${EntrySize*8}</td>`;
177+
tbody += `<td>&nbsp;</td>`;
178+
tbody += `<td>&nbsp;</td>`;
179+
tbody += `<td>${formatMemory(EntrySize*8)}</td>`;
180+
for(var scol of scols) {
181+
tbody += `<td>${formatMemory(scol.raw)}</td>`;
182+
}
183+
tbody += "</tr>";
184+
185+
calc_tbody.innerHTML = tbody;
186+
}
187+
188+
calc_input_es.oninput = update;
189+
calc_input_me.oninput = update;
190+
calc_input_cs.onchange = update;
191+
update();
192+
calculator.style = '';
193+
}) ();
194+
</script>

0 commit comments

Comments
 (0)