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 +" | 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.