PageSourceSearch

https://retroapplejs.github.io/res/EMU_DEVICE_video_GPU.js

js retroapplejs.github.io collected 2026-10-03 09:50:40 UTC 22,701 bytes, 608 lines download raw bytes

1//
2// Copyright (c) 2024 Freddy Vandriessche.
3// notice: https://raw.githubusercontent.com/RetroAppleJS/AppleII-IDE/main/LICENSE.md
4//
5// apple2GPU.js
6
7//
8// Optional configuration before loading this file:
9//   window.Apple2VideoGPU_CONFIG = {
10//       frameRate: 50
11//   };
12//
13// frameRate is the maximum dirty-frame submission rate. The browser's
14// requestAnimationFrame cadence remains the final presentation limit.
15
16var APPLE2_GPU_CFG_DEFAULT =
17{
18    frameRate: 50
19};
20
21var APPLE2_GPU_FRAME_RATE_SPEC =
22{
23    min: 10,
24    max: 60,
25    step: 5,
26    cpuClockHz: 1000000
27};
28
29function Apple2VideoGPU_copyConfig()
30{
31    var cfg = {}, user = window.Apple2VideoGPU_CONFIG || {};
32
33    for (var key in APPLE2_GPU_CFG_DEFAULT)
34        cfg[key] = APPLE2_GPU_CFG_DEFAULT[key];
35
36    for (var userKey in user)
37        cfg[userKey] = user[userKey];
38
39    return cfg;
40}
41
42function Apple2VideoGPU_clampFrameRate(value)
43{
44    value = Number(value);
45
46    if (!isFinite(value))
47        value = APPLE2_GPU_CFG_DEFAULT.frameRate;
48
49    return Math.max(
50        APPLE2_GPU_FRAME_RATE_SPEC.min,
51        Math.min(APPLE2_GPU_FRAME_RATE_SPEC.max,value)
52    );
53}
54
55function Apple2VideoGPU_getControlInstance()
56{
57    if (typeof(oApple2Video) === "undefined" || oApple2Video === null ||
58        typeof(oApple2Video.getRendererInstance) !== "function")
59        return null;
60
61    return oApple2Video.getRendererInstance("gpu",true);
62}
63
64function Apple2VideoGPU_setFrameRateControl(input)
65{
66    var rendererGPU = Apple2VideoGPU_getControlInstance();
67    if (!rendererGPU || typeof(rendererGPU.setFrameRate) !== "function")
68        return;
69
70    var value = rendererGPU.setFrameRate(Number(input.value));
71    input.value = value;
72
73    var output = document.getElementById("gpuFrameRateValue");
74    if (output)
75        output.textContent = Math.round(value) + " fps";
76}
77
78function Apple2VideoGPU_setFrameRateFeedback(flag)
79{
80    var rendererGPU = Apple2VideoGPU_getControlInstance();
81    if (!rendererGPU || typeof(rendererGPU.setFrameRateFeedbackEnabled) !== "function")
82        return;
83
84    rendererGPU.setFrameRateFeedbackEnabled(flag);
85}
86
87function Apple2VideoGPU_syncMeasuredFrameRate(value)
88{
89    var output = document.getElementById("gpuFrameRateMeasuredValue");
90    if (output)
91        output.textContent = Number(value).toFixed(1) + " fps";
92}
93
94function Apple2VideoGPU_controls(rendererGPU)
95{
96    var frameRate = rendererGPU && typeof(rendererGPU.getFrameRate) === "function"
97        ? rendererGPU.getFrameRate()
98        : APPLE2_GPU_CFG_DEFAULT.frameRate;
99    var measuredRate = rendererGPU && typeof(rendererGPU.getFrameRateMeasured) === "function"
100        ? rendererGPU.getFrameRateMeasured()
101        : 0;
102
103    return "<div class=\"appbut mini\""
104        +" onmouseover=\"Apple2VideoGPU_setFrameRateFeedback(true)\""
105        +" onmouseout=\"Apple2VideoGPU_setFrameRateFeedback(false)\""
106        +" title=\"Hover here to hear the measured submitted-frame cadence.\">"
107        +"<input id=\"gpuFrameRate\" type=\"range\""
108        +" min=\""+APPLE2_GPU_FRAME_RATE_SPEC.min+"\""
109        +" max=\""+APPLE2_GPU_FRAME_RATE_SPEC.max+"\""
110        +" step=\""+APPLE2_GPU_FRAME_RATE_SPEC.step+"\""
111        +" value=\""+Math.round(frameRate)+"\" class=\"slider\""
112        +" oninput=\"Apple2VideoGPU_setFrameRateControl(this)\""
113        +" style=\"width:65px;float:left\"></input>"
114        +"<div style=\"float:left;border:0px solid;padding:2px 0px 0px 10px;\">"
115        +"max frame rate "
116        +"<span id=\"gpuFrameRateValue\">"+Math.round(frameRate)+" fps</span>"
117        +"&nbsp;|&nbsp;measured "
118        +"<span id=\"gpuFrameRateMeasuredValue\">"+Number(measuredRate).toFixed(1)+" fps</span>"
119        +"</div></div><br>";
120}
121
122// Function declarations are hoisted, so constructor metadata can be attached
123// before the renderer alias is captured by index.html.
124Apple2Video.ctrl_dlg = function()
125{
126    return Apple2VideoGPU_controls(null);
127};
128
129if(oEMU===undefined) var oEMU = {"component":{"Video":{"Apple2Video":new Apple2Video()}}}
130else oEMU.component.Video.Apple2Video = new Apple2Video();
131
132function Apple2Video(ctx)
133{
134    this.id = {
135         "DCODE":"A2GPU"
136        ,"coID":"Apple2Video"
137        ,"hostPCODE":"A2BO"
138        ,"deviceIdx":0
139        ,"icon":"fa fa-eye"
140        ,"description":"Apple II GPU video"
141        ,"deviceEnable":true
142    };
143
144    var bDebug_snd = false;
145    var renderCfg = Apple2VideoGPU_copyConfig();
146
147    var gfx_mode;
148    var mix_mode;
149    var page2_mode;
150    var hires_mode;
151    var chrome_mode = 0;
152    var flash_on = false;
153    var flash_count = 0;
154    var frame_count = 0;
155    var frame_redraw = true;
156    var frame_rate = Apple2VideoGPU_clampFrameRate(renderCfg.frameRate);
157    var frame_interval_ms = 1000/frame_rate;
158    function videoClockHz()
159    {
160        return typeof(_o)!="undefined" && Number.isFinite(_o.CPU_ClocksTicks_s)
161            ? _o.CPU_ClocksTicks_s
162            : APPLE2_GPU_FRAME_RATE_SPEC.cpuClockHz;
163    }
164    var video_clock_hz = videoClockHz();
165    var frame_interval_ticks = video_clock_hz/frame_rate;
166    var flash_interval_ticks = video_clock_hz/4;
167    var frame_submit_count = 0;
168    var frame_measurement_start_ms = 0;
169    var frame_rate_measured = 0;
170
171    if(ctx) this.ctx = ctx;
172
173    this.charRom = Apple2CharROM_get("A2_US");
174    this.charRomKey = "A2_US";
175    this.ctrl_dlg = function() { return Apple2VideoGPU_controls(this); };
176    this.setCharRom = function(rom, key) { this.charRom = rom; this.charRomKey = key; frame_redraw = true; return key; };
177    this.vidram = null; // apple2hw.js sets this to give me reference to ram
178    this.hw = null;     // apple2hw provides its reference during initialisation
179    this.gpu = null;    // keep one GPU.js/WebGL owner for the lifetime of this renderer
180
181    // BYTE ARRAY SERIALISATION
182    this.serial8 = new Uint8Array();
183    this.config8_map = {};
184    this.config8_idx = {};
185    this.ser8_ref = function(name,arr) { this.setlen(name,null); this.serial8 = new Uint8Array([...this.serial8,...arr]) }
186    this.ser8_val = function(name,val,modifier) { this.setlen(name,modifier); this.serial8 = new Uint8Array([...this.serial8,...[val&255]]) } 
187    this.ser8_map = function() { return "const "+JSON.stringify(this.config8_map).replace(/"|\{|\}/g,"").replace(/:/g,"=")+";" }
188    this.idx8 = function(name) { return this.config8_idx[name] }
189    this.setlen = function(name,modifier)
190    {
191        if(modifier===undefined) var modifier = ["cfg[","]"];
192        else if(modifier==null)  var modifier = ["",""];
193        if(this.config8_map[name]===undefined)
194        {
195        this.config8_map[name] = modifier[0] + this.serial8.length + modifier[1];
196        this.config8_idx[name] = this.serial8.length;
197        }
198    }
199
200    this.getFrameRate = function()
201    {
202        return frame_rate;
203    };
204
205    this.getFrameIntervalMs = function()
206    {
207        return frame_interval_ms;
208    };
209
210    this.getFrameCycleThreshold = function()
211    {
212        // Backward-compatible alias from the first control implementation.
213        // Timing is no longer cycle-based.
214        return frame_interval_ticks;
215    };
216
217    this.getFrameRateMeasured = function()
218    {
219        return frame_rate_measured;
220    };
221
222    this.setFrameRateFeedbackEnabled = function(flag)
223    {
224        bDebug_snd = !!flag;
225        return bDebug_snd;
226    };
227
228    this.nowMs = function()
229    {
230        return (typeof(performance) !== "undefined" && typeof(performance.now) === "function")
231            ? performance.now()
232            : Date.now();
233    };
234
235    this.setFrameRate = function(value)
236    {
237        frame_rate = Apple2VideoGPU_clampFrameRate(value);
238        renderCfg.frameRate = frame_rate;
239        frame_interval_ms = 1000/frame_rate;
240        frame_interval_ticks = video_clock_hz/frame_rate;
241
242        frame_count = 0;
243        frame_redraw = true;
244        return frame_rate;
245    };
246
247    this.recordFrameSubmit = function(now)
248    {
249        if (!isFinite(now))
250            now = this.nowMs();
251
252        if (!frame_measurement_start_ms)
253            frame_measurement_start_ms = now;
254
255        frame_submit_count++;
256        this.updateFrameRateMeasurement(now);
257    };
258
259    this.updateFrameRateMeasurement = function(now)
260    {
261        if (!isFinite(now))
262            now = this.nowMs();
263
264        if (!frame_measurement_start_ms)
265            frame_measurement_start_ms = now;
266
267        var elapsed = now - frame_measurement_start_ms;
268        if (elapsed < 1000)
269            return frame_rate_measured;
270
271        frame_rate_measured = frame_submit_count * 1000 / elapsed;
272        frame_submit_count = 0;
273        frame_measurement_start_ms = now;
274
275        if (typeof(Apple2VideoGPU_syncMeasuredFrameRate) === "function")
276            Apple2VideoGPU_syncMeasuredFrameRate(frame_rate_measured);
277
278        return frame_rate_measured;
279    };
280
281    this.reset = function()
282    {
283        gfx_mode    = false;
284        mix_mode    = false;
285        page2_mode  = false;
286        hires_mode  = false;
287        flash_on    = true;
288        flash_count = 0;
289        frame_count = 0;
290        video_clock_hz = videoClockHz();
291        frame_interval_ticks = video_clock_hz/frame_rate;
292        flash_interval_ticks = video_clock_hz/4;
293        frame_submit_count = 0;
294        frame_measurement_start_ms = this.nowMs();
295        frame_rate_measured = 0;
296        chrome_mode = 0;
297        this.register_mode();
298
299        // INSTANTIATE CREATE GPU & KERNEL + CONFIGURE DATA SERIALISATION
300        const video = this;
301        const vidContext = ctx;
302        if(!video.gpu)
303        {
304            video.initGPU(
305                { canvas:  vidContext, mode: 'gpu'}
306                ,{ kernel: video.kProcess_v6, output: [vidContext.width,vidContext.height], graphical: true }
307                ,{"CHROME_MODE":[video.getChromeMode(),"cfg[","]<<2"]
308                ,"DIM_H":[vidContext.height>>1]
309                ,"GFX_FLG":[video.register_mode()]
310                ,"FLASH":[false]
311                ,"PALETTE":[video.INTCols]});
312        }
313            //console.log( video.ser8_map() )
314    }
315
316    this.register_mode = function()
317    {
318      this.modes = {"gfx":gfx_mode,"mix":mix_mode,"page2":page2_mode,"hires":hires_mode,"chrome":chrome_mode};
319      var ret = (gfx_mode?1:2) + (mix_mode?8:4) + (page2_mode?32:16) + (hires_mode?128:64);
320      return ret;
321    }
322
323    this.cycle = function(ticks)
324    {
325      ticks = Number(ticks);
326      if(!Number.isFinite(ticks) || ticks<=0) return;   
327      frame_count += ticks;
328      flash_count += ticks;
329      
330      var flashToggles = Math.floor(flash_count/flash_interval_ticks);
331      if(flashToggles>0)
332      {
333        flash_count -= flashToggles*flash_interval_ticks;
334
335        if((flashToggles&1)!=0)
336        {
337          flash_on = !flash_on;
338          frame_redraw = true;
339        }
340      }
341      
342      if(frame_redraw && frame_count>=frame_interval_ticks)
343      {
344        frame_count %= frame_interval_ticks;
345        this.redraw();
346      }
347
348    }
349
350    // Redraw flashing characters only (including cursor).  Called every time flash_on toggles.
351    //this.reflash = function() { frame_redraw = true; this.redraw() }
352
353  this.redraw = function()
354  {
355      this.serial8[ this.idx8("CHROME_MODE") ] = chrome_mode;
356      this.serial8[ this.idx8("GFX_FLG") ]     = this.register_mode();
357      this.serial8[ this.idx8("FLASH") ]       = flash_on ? 1 : 0;
358
359      const video = this;
360
361window.requestAnimationFrame(function(rafNow)
362{
363    if (!video.hw || typeof(video.hw.safe_videodump) !== "function") return;
364    if (typeof(video.kernel) !== "function") return;
365
366    video.kernel(
367        video.hw.safe_videodump(),
368        video.charRom,
369        video.serial8
370    );
371
372    video.recordFrameSubmit(Number(rafNow));
373    if(bDebug_snd && oEMU && oEMU.component && oEMU.component.IO && oEMU.component.IO.AppleSpeaker
374     && typeof(oEMU.component.IO.AppleSpeaker.toggle) === "function")
375        oEMU.component.IO.AppleSpeaker.toggle();
376});
377
378      frame_redraw = false;
379  }
380
381    this.write = function(addr, d8) // FLOATING BUS behavior ?
382    {
383        if (this.vidram[addr] == d8) return;   // biggest cheap win
384        this.vidram[addr] == d8;
385        frame_redraw = frame_redraw || this.addrVisible(addr);
386        //if(bDebug_snd && frame_redraw) oEMU.component.IO.AppleSpeaker.toggle();  // toggle speaker at each key frame
387    }
388
389    this.setGfx   = function(flag) { if (gfx_mode != flag)   { gfx_mode = flag; frame_redraw = true } }
390    this.setMix   = function(flag) { if (mix_mode != flag)   { mix_mode = flag; frame_redraw = true } }
391    this.setPage2 = function(flag) { if (page2_mode != flag) { page2_mode = flag; frame_redraw = true } }
392    this.setHires = function(flag) { if (hires_mode != flag) { hires_mode = flag; frame_redraw = true } }
393
394    this.addrVisible = function(addr)
395    {
396        const a = addr & 0xFFFF;
397        const lo7 = a & 0x7F;
398
399        // Apple II screen holes: last 8 bytes of each 128-byte block.
400        const scrByte = lo7 < 0x78;
401
402        // Current HGR kernel also reads adr-1 / adr+1, so edge-adjacent holes may affect pixels.
403        const hgrByte = scrByte | (lo7 == 0x78) | (lo7 == 0x7F);
404
405        // row >= 20 test for mixed-mode text bottom / hidden HGR bottom.
406        const mixBottom = ((lo7 >= 0x50) & ((a >> 7) & 4));
407
408        const txtBase = page2_mode ? 0x0800 : 0x0400;
409        const hgrBase = page2_mode ? 0x4000 : 0x2000;
410
411        const inTxt = ((a - txtBase) >>> 0) < 0x0400;
412        const inHgr = ((a - hgrBase) >>> 0) < 0x2000;
413
414        if (!gfx_mode)                // TEXT
415            return inTxt & scrByte;
416
417        if (!hires_mode)              // LORES / mixed LORES
418            return inTxt & scrByte;
419
420        // HIRES / mixed HIRES
421        return (inHgr & hgrByte & (!mix_mode | !mixBottom)) |
422              (mix_mode & inTxt & scrByte & mixBottom);
423    }
424
425    this.setMonitor = function(mode)
426    {
427      chrome_mode = mode & 3;
428      frame_redraw = true;
429      return  { "color": _CFG_CHROMA[chrome_mode].COL_num?_CFG_CHROMA[chrome_mode].COL_num:"#000000"
430                ,"name": _CFG_CHROMA[chrome_mode].COL_name}
431    }
432
433    this.getloresCols = function() {
434        return loresCols;
435    }
436
437    this.gethiresCols = function() {
438        return hiresCols;
439    }
440
441    this.getChromeMode = function() {
442        return chrome_mode;
443    }
444
445    this.setCol = function(idx,column,val) {
446        return loresCols[idx][column] = val;
447    }
448
449
450    // Lores color to RGB table. (* Hires)
451    var loresCols = [
452    ["#000000","#000000","#000000","#000000","Black"]  // *
453    ,["#901740","#4D4D4D","#304D48","#4C4631","Magenta"] 
454    ,["#402CA5","#5B5B5B","#395B56","#5A5239","Dark Blue"] 
455    ,["#D143E6","#A8A8A8","#69A89E","#A6986A","Purple"]  // *
456    ,["#006940","#383838","#233835","#383324","Dark Green"] 
457    ,["#808080","#808080","#508078","#7E7451","Grey 1"] 
458    ,["#2F96E6","#8E8E8E","#598E85","#8C8059","Medium Blue"]  // *
459    ,["#BFABFF","#CECECE","#81CEC2","#CBBA82","Light Blue"] 
460    ,["#405400","#313131","#1F312E","#312D1F","Brown"] 
461    ,["#D06B1A","#717171","#47716B","#706748","Orange"]  // *
462    ,["#808080","#808080","#508078","#7E7451","Grey 2"] 
463    ,["#FF96BF","#C7C7C7","#7DC7BB","#C4B47D","Pink"] 
464    ,["#30BD1B","#575757","#375752","#564F37","Light Green"]  // *
465    ,["#BFD35A","#A4A4A4","#67A49A","#A29568","Yellow"] 
466    ,["#6FE8BF","#B2B2B2","#70B2A8","#B0A170","Aquamarine"] 
467    ,["#FFFFFF","#FFFFFF","#A0FFF0","#FCE7A1","White"]  // *
468    ];
469
470    var hiresCols = [
471     loresCols[0]
472    ,loresCols[3]
473    ,loresCols[6]
474    ,loresCols[9]
475    ,loresCols[12]
476    ,loresCols[15]
477    ];
478
479    this.INTCols = new Uint8Array([
480        0X00,0X00,0X00,0X0,0x00,0x00,0x00,0x0,0x00,0x00,0x00,0x0,0x00,0x00,0x00,0x0  // Black
481       ,0X90,0X17,0X40,0X0,0x4D,0x4D,0x4D,0x0,0x30,0x4D,0x48,0x0,0x4C,0x46,0x31,0x0  // Magenta
482       ,0X40,0X2C,0XA5,0X0,0x5B,0x5B,0x5B,0x0,0x39,0x5B,0x56,0x0,0x5A,0x52,0x39,0x0  // Dark Blue
483       ,0XD0,0X43,0XE5,0X0,0xA8,0xA8,0xA8,0x0,0x69,0xA8,0x9E,0x0,0xA6,0x98,0x6A,0x0  // Purple
484       ,0X00,0X69,0X40,0X0,0x38,0x38,0x38,0x0,0x23,0x38,0x35,0x0,0x38,0x33,0x24,0x0  // Dark Green
485       ,0X80,0X80,0X80,0X0,0x80,0x80,0x80,0x0,0x50,0x80,0x78,0x0,0x7E,0x74,0x51,0x0  // Grey 1
486       ,0X2F,0X95,0XE5,0X0,0x8E,0x8E,0x8E,0x0,0x59,0x8E,0x85,0x0,0x8C,0x80,0x59,0x0  // Medium Blue
487       ,0XBF,0XAB,0XFF,0X0,0xCE,0xCE,0xCE,0x0,0x81,0xCE,0xC2,0x0,0xCB,0xBA,0x82,0x0  // Light Blue
488       ,0X40,0X54,0X00,0X0,0x31,0x31,0x31,0x0,0x1F,0x31,0x2E,0x0,0x31,0x2D,0x1F,0x0  // Brown
489       ,0XD0,0X6A,0X1A,0X0,0x71,0x71,0x71,0x0,0x47,0x71,0x6B,0x0,0x70,0x67,0x48,0x0  // Orange
490       ,0X80,0X80,0X80,0X0,0x80,0x80,0x80,0x0,0x50,0x80,0x78,0x0,0x7E,0x74,0x51,0x0  // Grey 2
491       ,0XFF,0X96,0XBF,0X0,0xC7,0xC7,0xC7,0x0,0x7D,0xC7,0xBB,0x0,0xC4,0xB4,0x7D,0x0  // Pink
492       ,0X2F,0XBC,0X1A,0X0,0x57,0x57,0x57,0x0,0x37,0x57,0x52,0x0,0x56,0x4F,0x37,0x0  // Light Green
493       ,0XBF,0XD3,0X5A,0X0,0xA4,0xA4,0xA4,0x0,0x67,0xA4,0x9A,0x0,0xA2,0x95,0x68,0x0  // Yellow  
494       ,0X6F,0XE8,0XBF,0X0,0xB2,0xB2,0xB2,0x0,0x70,0xB2,0xA8,0x0,0xB0,0xA1,0x70,0x0  // Aquamarine
495       ,0XFF,0XFF,0XFF,0X0,0xFF,0xFF,0xFF,0x0,0xA0,0xFF,0xF0,0x0,0xFC,0xE7,0xA1,0x0  // White
496       ]);
497
498    // TODO USE INTCOLS instead of loresCols
499    this.hgr_PixelColor = function(x, y, left, me, right, b7)
500    {
501        var a0 = x & 0x01;
502
503        // If pixel is set and either adjacent pixels are set, it's white.
504        if (me != 0 && (left != 0 || right != 0))
505            return loresCols[15][0];  // White
506        // If pixel is set but no adjacent pixels are set, pick a color
507        // based on column and b7.
508        else if (me != 0) {
509            if (b7 != 0) {
510                if (a0 != 0) return loresCols[9][0]; // Orange
511                else         return loresCols[6][0]; // Medium Blue
512            } else {
513                if (a0 != 0) return loresCols[12][0]; // Green
514                else         return loresCols[3][0];  // Purple
515            }
516        }
517        // If pixel is not set and both adjacent pixels are set, pick a
518        // color based on column (of adjacent pixel) and b7 (of this byte).
519        else if (left != 0 && right != 0) {
520            if (b7 != 0) {
521                if (a0 != 0) return loresCols[6][0]; // Medium Blue
522                else         return loresCols[9][0]; // Orange
523            } else {
524                if (a0 != 0) return loresCols[3][0];  // Purple
525                else         return loresCols[12][0]; // Green
526            }
527        }
528        // Else it's black.
529        else
530           return loresCols[0][0];    // Black
531    }
532
533  this.initGPU = function(GPUarg,KERNELarg,config)
534  { 
535    if(this.gpu && typeof(this.kernel) === "function")
536        return this.kernel;
537
538    // CONFIGURE DATA SERIALISATION
539    for(var i in config)
540    {
541      if(config[i][0].constructor === Uint8Array) this.ser8_ref(i,config[i][0])
542      else this.ser8_val(i,config[i][0],config[i][1]===undefined?undefined:[config[i][1],config[i][2]])
543    }
544
545    // INSTATIATE GPU CLASS & ATTACH TO WINDOW OBJECT
546    try { var gpu = new window.GPU.GPU(GPUarg) } catch (e) { var gpu = new GPU(GPUarg) }
547
548    // TRANSPILE CHOSEN KERNEL SCRIPT
549    var kernel = gpu.createKernel(KERNELarg.kernel,KERNELarg)
550    this.gpu = gpu;
551    this.kernel = kernel;
552    return kernel;
553  }
554
555  this.kernel = function() { alert.warn("initialise first with initGPU()") }
556
557//   ██████  ██████  ██    ██     ██   ██ ███████ ██████  ███    ██ ███████ ██      
558//  ██       ██   ██ ██    ██     ██  ██  ██      ██   ██ ████   ██ ██      ██      
559//  ██   ███ ██████  ██    ██     █████   █████   ██████  ██ ██  ██ █████   ██      
560//  ██    ██ ██      ██    ██     ██  ██  ██      ██   ██ ██  ██ ██ ██      ██      
561//   ██████  ██       ██████      ██   ██ ███████ ██   ██ ██   ████ ███████ ███████ 
562
563  this.kProcess_v6 =  function(mem,rom,cfg)
564  { 
565    const CHROME_MODE=cfg[0]<<2,DIM_H=cfg[1],GFX_FLG=cfg[2],FLASH=cfg[3],PALETTE=4
566
567    const xp = (this.thread.x>>1);
568    const yp = DIM_H-(this.thread.y>>1)-1;
569    const x7 = xp+1+(xp+1+(xp+1>>3)>>3)>>3;    // equivalent to Math.floor(xp/7)
570    const m7 = xp-(x7<<3)+x7;                  // equivalent to xp%7
571    const adr_ofs = yp<<4 & 0x380 | yp>>3 & 0x18 | yp>>1 & 0x60;
572
573    const PAGE  = (GFX_FLG & 48) << 9;
574    const MIX   = ((1^(yp+0x60>>8) & (GFX_FLG>>3)) | (GFX_FLG>>2)) & 1
575    const HIRES = (GFX_FLG & GFX_FLG>>7 & MIX) & 1;
576    const LORES = (GFX_FLG & GFX_FLG>>6 & MIX) & 1;
577
578    var cp = PALETTE + CHROME_MODE;
579
580    if(HIRES==1)
581    {
582      const adr  = PAGE + (yp<<10 & 0x1C00 | adr_ofs) + x7                // locate hardware address
583      const b171 = (0x7F & mem[adr+1])<<8  | (0x7F & mem[adr])<<1 | (mem[adr-1]>>6 & 1);   // aggregate left - center - right byte
584      const b3   = b171>>m7&7, d1 = 0b100100>>b3&1, d2 = 0b11001000>>b3&1;// 3-bits around (xp,yp) + decision logic
585      const dl   = 1+((xp^b3)<<1&2|(xp^b3)&1^mem[adr]>>7)&(d1<<4)-d1      // main hi-res color decision logic (6 states 0-5 to multiply by 3)
586      cp += (dl+(dl<<1) | (d2<<4)-d2)<<4;
586                                 // 3*dl | 15*d2 = color pointer (offset 16 positions per color)
587    }
588    else if(LORES==1)
589    {
590      const adr = (PAGE>>3) + adr_ofs + x7;                               // locate hardware address
591      const d8 = mem[adr];                                                // read byte from memory
592      const y4 = (yp>>2&1)<<2;                                            // hi/lo nibble filter
593      cp += ((d8>>y4&15)<<4);                                             // color pointer
594    }   
595    else  // TEXT
596    {
597      const adr   = (PAGE>>3) + adr_ofs + x7;                               // locate hardware address
598      const d8    = mem[adr];                                               // read byte from memory
599      const offs  = ((d8 & 0x3F) ^ 0x20) << 3;                              // character offset (not inversed or flashing)
600      const cbyte = rom[ offs + (yp&7) ];                                   // read byte from character ROM
601      const infl  = d8>>6 & 3;                                              // inv/flash modifier
602      const rgb   = (cbyte>>m7&1) ^ (0b001>>infl&1) ^ (0b010>>infl&FLASH);  // color 1 or 0 with FLASH modifier
603      cp += (rgb<<8) - (rgb<<4);                                            // color pointer
604    }
605
606    this.color(cfg[cp]/256,cfg[cp+1]/256,cfg[cp+2]/256);
607  }
608}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.