mirror of
https://github.com/zellyn/apple2shader.git
synced 2024-12-26 23:29:24 +00:00
webgl: first pass of configureShaders done
This commit is contained in:
parent
797953592f
commit
64131564d5
12
index.html
12
index.html
@ -227,11 +227,19 @@ void main() {
|
|||||||
let gl = canvas.getContext("webgl");
|
let gl = canvas.getContext("webgl");
|
||||||
let sv = new screenEmu.ScreenView(gl);
|
let sv = new screenEmu.ScreenView(gl);
|
||||||
await sv.initOpenGL();
|
await sv.initOpenGL();
|
||||||
|
|
||||||
|
let imageInfo = new screenEmu.ImageInfo(0, 0, 0, 0, 0, 0, new ImageData(10, 10));
|
||||||
|
let displayConfig = new screenEmu.DisplayConfiguration();
|
||||||
|
sv.image = imageInfo;
|
||||||
|
sv.displayConfiguration = displayConfig;
|
||||||
|
sv.vsync();
|
||||||
|
|
||||||
sv.freeOpenGL();
|
sv.freeOpenGL();
|
||||||
}
|
}
|
||||||
|
|
||||||
tryScreenView().then(() => console.log('tryScreenView: success'))
|
tryScreenView().then(() => console.log('tryScreenView: success'));
|
||||||
.catch((e) => console.log(`tryScreenView: error: ${e}`));
|
|
||||||
|
const w = screenEmu.Vector.lanczosWindow(17, 0.419047624);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
133
screenEmu.js
133
screenEmu.js
@ -404,7 +404,8 @@ void main(void)
|
|||||||
// returns: a canvas
|
// returns: a canvas
|
||||||
const screenData = (image, details) => {
|
const screenData = (image, details) => {
|
||||||
if ((image.naturalWidth != 560) || (image.naturalHeight != 192)) {
|
if ((image.naturalWidth != 560) || (image.naturalHeight != 192)) {
|
||||||
throw `screenData expects an image 560x192; got ${image.naturalWidth}x${image.naturalHeight}`;
|
throw new Error('screenData expects an image 560x192;' +
|
||||||
|
` got ${image.naturalWidth}x${image.naturalHeight}`);
|
||||||
}
|
}
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
const context = canvas.getContext('2d');
|
const context = canvas.getContext('2d');
|
||||||
@ -463,7 +464,7 @@ void main(void)
|
|||||||
|
|
||||||
const log = gl.getShaderInfoLog(shader);
|
const log = gl.getShaderInfoLog(shader);
|
||||||
gl.deleteShader(shader);
|
gl.deleteShader(shader);
|
||||||
throw `unable to compile shader ${name}: \n${log}`;
|
throw new Error(`unable to compile shader ${name}: \n${log}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Code from:
|
// Code from:
|
||||||
@ -480,7 +481,7 @@ void main(void)
|
|||||||
}
|
}
|
||||||
const log = gl.getProgramInfoLog(program);
|
const log = gl.getProgramInfoLog(program);
|
||||||
gl.deleteProgram(program);
|
gl.deleteProgram(program);
|
||||||
throw `unable to compile program ${name}: \n${log}`;
|
throw new Error(`unable to compile program ${name}: \n${log}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const TextureInfo = class {
|
const TextureInfo = class {
|
||||||
@ -524,6 +525,12 @@ void main(void)
|
|||||||
const ImageInfo = class {
|
const ImageInfo = class {
|
||||||
constructor(sampleRate, blackLevel, whiteLevel, subCarrier, colorBurst,
|
constructor(sampleRate, blackLevel, whiteLevel, subCarrier, colorBurst,
|
||||||
phaseAlternation, data) {
|
phaseAlternation, data) {
|
||||||
|
if (typeof data != "object") {
|
||||||
|
throw new Error(`want typeof data == 'object'; got '${typeof data}'`);
|
||||||
|
}
|
||||||
|
if (!(data instanceof ImageData)) {
|
||||||
|
throw new Error(`want data instanceof ImageData; got '${data.constructor.name}'`);
|
||||||
|
}
|
||||||
this.sampleRate = sampleRate;
|
this.sampleRate = sampleRate;
|
||||||
this.blackLevel = blackLevel;
|
this.blackLevel = blackLevel;
|
||||||
this.whiteLevel = whiteLevel;
|
this.whiteLevel = whiteLevel;
|
||||||
@ -544,14 +551,30 @@ void main(void)
|
|||||||
|
|
||||||
const ScreenView = class {
|
const ScreenView = class {
|
||||||
constructor(gl) {
|
constructor(gl) {
|
||||||
|
const float_texture_ext = gl.getExtension('OES_texture_float');
|
||||||
|
if (float_texture_ext == null) {
|
||||||
|
throw new Error("WebGL extension 'OES_texture_float' unavailable");
|
||||||
|
}
|
||||||
|
|
||||||
this.gl = gl;
|
this.gl = gl;
|
||||||
this.textures = {};
|
this.textures = {};
|
||||||
this.shaders = {};
|
this.shaders = {};
|
||||||
this.image = null;
|
this.image = null;
|
||||||
|
this.display = null;
|
||||||
|
this.configurationChanged = true;
|
||||||
|
this.imageChanged = true;
|
||||||
|
this.imageSampleRate = null;
|
||||||
|
this.imageBlackLevel = null;
|
||||||
|
this.imageWhiteLevel = null;
|
||||||
|
this.imageSubcarrier = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
set image(imageInfo) {
|
get image() {
|
||||||
this.imageInfo = imageInfo;
|
return this._image
|
||||||
|
}
|
||||||
|
|
||||||
|
set image(image) {
|
||||||
|
this._image = image;
|
||||||
this.imageChanged = true;
|
this.imageChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -622,8 +645,6 @@ void main(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadShader(name, source) {
|
loadShader(name, source) {
|
||||||
console.log(`ScreenView.loadShader(${name}): not implemented yet`);
|
|
||||||
|
|
||||||
const glVertexShader = createShader(this.gl, name, this.gl.VERTEX_SHADER, VERTEX_SHADER);
|
const glVertexShader = createShader(this.gl, name, this.gl.VERTEX_SHADER, VERTEX_SHADER);
|
||||||
const glFragmentShader = createShader(this.gl, name, this.gl.FRAGMENT_SHADER, source);
|
const glFragmentShader = createShader(this.gl, name, this.gl.FRAGMENT_SHADER, source);
|
||||||
const glProgram = createProgram(this.gl, name, glVertexShader, glFragmentShader);
|
const glProgram = createProgram(this.gl, name, glVertexShader, glFragmentShader);
|
||||||
@ -664,16 +685,17 @@ void main(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
uploadImage() {
|
uploadImage() {
|
||||||
const image = this.imageInfo;
|
const gl = this.gl;
|
||||||
|
const image = this.image;
|
||||||
|
|
||||||
this.resizeTexture("IMAGE_IN", image.width, image.height);
|
this.resizeTexture("IMAGE_IN", image.width, image.height);
|
||||||
const texInfo = this.textures["IMAGE_IN"];
|
const texInfoImage = this.textures["IMAGE_IN"];
|
||||||
gl.bindTexture(gl.TEXTURE_2D, texInfo.glTexture);
|
gl.bindTexture(gl.TEXTURE_2D, texInfoImage.glTexture);
|
||||||
const format = gl.LUMINANCE;
|
const format = gl.LUMINANCE;
|
||||||
const type = gl.UNSIGNED_BYTE;
|
const type = gl.UNSIGNED_BYTE;
|
||||||
gl.texSubImage2D(gl.TEXTURE_2D, 0,
|
gl.texSubImage2D(gl.TEXTURE_2D, 0,
|
||||||
0, 0, // xoffset, yoffset
|
0, 0, // xoffset, yoffset
|
||||||
format, type, image.data);
|
format, type, image.data);
|
||||||
|
|
||||||
// Update configuration
|
// Update configuration
|
||||||
if ((image.sampleRate != this.imageSampleRate) ||
|
if ((image.sampleRate != this.imageSampleRate) ||
|
||||||
@ -702,10 +724,10 @@ void main(void)
|
|||||||
phaseInfo[3 * x + 1] = phaseAlternation[x % phaseAlternation.length];
|
phaseInfo[3 * x + 1] = phaseAlternation[x % phaseAlternation.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
texInfo = this.textures("IMAGE_PHASEINFO");
|
const texInfoPhase = this.textures["IMAGE_PHASEINFO"];
|
||||||
gl.bindTexture(gl.TEXTURE_2D, texInfo.glTexture);
|
gl.bindTexture(gl.TEXTURE_2D, texInfoPhase.glTexture);
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, texHeight, 0,
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, texHeight, 0,
|
||||||
gl.RGB, gl.FLOAT, phaseInfo);
|
gl.RGB, gl.FLOAT, phaseInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRenderShader() {
|
getRenderShader() {
|
||||||
@ -768,10 +790,10 @@ void main(void)
|
|||||||
wy = wy.normalize();
|
wy = wy.normalize();
|
||||||
|
|
||||||
wu = w.mul(Vector.lanczosWindow(17, uBandwidth));
|
wu = w.mul(Vector.lanczosWindow(17, uBandwidth));
|
||||||
wu = wu.normalize() * 2;
|
wu = wu.normalize().mul(2);
|
||||||
|
|
||||||
wv = w.mul(Vector.lanczosWindow(17, vBandwidth));
|
wv = w.mul(Vector.lanczosWindow(17, vBandwidth));
|
||||||
wv = wv.normalize() * 2;
|
wv = wv.normalize().mul(2);
|
||||||
} else {
|
} else {
|
||||||
wy = w.mul(Vector.lanczosWindow(17, bandwidth));
|
wy = w.mul(Vector.lanczosWindow(17, bandwidth));
|
||||||
wu = wv = wy = wy.normalize();
|
wu = wv = wy = wy.normalize();
|
||||||
@ -818,7 +840,7 @@ void main(void)
|
|||||||
// Disable color decoding when no subcarrier
|
// Disable color decoding when no subcarrier
|
||||||
if (isCompositeDecoder)
|
if (isCompositeDecoder)
|
||||||
{
|
{
|
||||||
if ((imageSubcarrier == 0.0) || this.display.videoWhiteOnly) {
|
if ((this.imageSubcarrier == 0.0) || this.display.videoWhiteOnly) {
|
||||||
decoderMatrix = new Matrix3(1, 0, 0,
|
decoderMatrix = new Matrix3(1, 0, 0,
|
||||||
0, 0, 0,
|
0, 0, 0,
|
||||||
0, 0, 0).mul(decoderMatrix);
|
0, 0, 0).mul(decoderMatrix);
|
||||||
@ -827,8 +849,8 @@ void main(void)
|
|||||||
|
|
||||||
// Saturation
|
// Saturation
|
||||||
decoderMatrix = new Matrix3(1, 0, 0,
|
decoderMatrix = new Matrix3(1, 0, 0,
|
||||||
0, display.videoSaturation, 0,
|
0, this.display.videoSaturation, 0,
|
||||||
0, 0, display.videoSaturation).mul(decoderMatrix);
|
0, 0, this.display.videoSaturation).mul(decoderMatrix);
|
||||||
|
|
||||||
// Hue
|
// Hue
|
||||||
const hue = 2 * Math.PI * this.display.videoHue;
|
const hue = 2 * Math.PI * this.display.videoHue;
|
||||||
@ -873,13 +895,76 @@ void main(void)
|
|||||||
0.317, -0.466, 1.677).mul(decoderMatrix);
|
0.317, -0.466, 1.677).mul(decoderMatrix);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw `unknown videoDecoder: ${this.display.videoDecoder}`;
|
throw new Error(`unknown videoDecoder: ${this.display.videoDecoder}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Brightness
|
// Brightness
|
||||||
|
const brightness = this.display.videoBrightness - this.imageBlackLevel;
|
||||||
|
let decoderOffset;
|
||||||
|
|
||||||
|
if (isCompositeDecoder)
|
||||||
|
decoderOffset = decoderMatrix.mul(new Matrix3(brightness, 0, 0,
|
||||||
|
0, 0, 0,
|
||||||
|
0, 0, 0));
|
||||||
|
else
|
||||||
|
decoderOffset = decoderMatrix.mul(new Matrix3(brightness, 0, 0,
|
||||||
|
brightness, 0, 0,
|
||||||
|
brightness, 0, 0));
|
||||||
|
|
||||||
// TODO(zellyn): implement the rest
|
gl.uniform3f(gl.getUniformLocation(renderShader, "decoderOffset"),
|
||||||
|
decoderOffset.at(0, 0),
|
||||||
|
decoderOffset.at(0, 1),
|
||||||
|
decoderOffset.at(0, 2));
|
||||||
|
|
||||||
|
// Contrast
|
||||||
|
let contrast = this.display.videoContrast;
|
||||||
|
|
||||||
|
const videoLevel = (this.imageWhiteLevel - this.imageBlackLevel);
|
||||||
|
if (videoLevel > 0)
|
||||||
|
contrast /= videoLevel;
|
||||||
|
else
|
||||||
|
contrast = 0;
|
||||||
|
|
||||||
|
if (contrast < 0)
|
||||||
|
contrast = 0;
|
||||||
|
|
||||||
|
decoderMatrix = decoderMatrix.mul(contrast);
|
||||||
|
|
||||||
|
gl.uniformMatrix3fv(gl.getUniformLocation(renderShader, "decoderMatrix"),
|
||||||
|
false, decoderMatrix.data);
|
||||||
|
|
||||||
|
// Display shader
|
||||||
|
gl.useProgram(displayShader);
|
||||||
|
|
||||||
|
// Barrel
|
||||||
|
gl.uniform1f(gl.getUniformLocation(displayShader, "barrel"),
|
||||||
|
this.display.displayBarrel);
|
||||||
|
|
||||||
|
// Shadow mask
|
||||||
|
gl.uniform1i(gl.getUniformLocation(displayShader, "shadowMask"), 1);
|
||||||
|
gl.uniform1f(gl.getUniformLocation(displayShader, "shadowMaskLevel"),
|
||||||
|
this.display.displayShadowMaskLevel);
|
||||||
|
|
||||||
|
// Persistence
|
||||||
|
const frameRate = 60;
|
||||||
|
|
||||||
|
gl.uniform1f(gl.getUniformLocation(displayShader, "persistenceLevel"),
|
||||||
|
this.display.displayPersistence /
|
||||||
|
(1.0 / frameRate + this.display.displayPersistence));
|
||||||
|
|
||||||
|
if (this.display.displayPersistence == 0)
|
||||||
|
this.resizeTexture("IMAGE_PERSISTENCE", 0, 0);
|
||||||
|
|
||||||
|
// Center lighting
|
||||||
|
let centerLighting = this.display.displayCenterLighting;
|
||||||
|
if (Math.abs(centerLighting) < 0.001)
|
||||||
|
centerLighting = 0.001;
|
||||||
|
gl.uniform1f(gl.getUniformLocation(displayShader, "centerLighting"),
|
||||||
|
1.0 / centerLighting - 1);
|
||||||
|
|
||||||
|
// Luminance gain
|
||||||
|
gl.uniform1f(gl.getUniformLocation(displayShader, "luminanceGain"),
|
||||||
|
this.display.displayLuminanceGain);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(zellyn): implement
|
// TODO(zellyn): implement
|
||||||
@ -896,8 +981,8 @@ void main(void)
|
|||||||
resizeTexture(name, width, height) {
|
resizeTexture(name, width, height) {
|
||||||
const gl = this.gl;
|
const gl = this.gl;
|
||||||
const texInfo = this.textures[name];
|
const texInfo = this.textures[name];
|
||||||
if (!!texInfo) {
|
if (!texInfo) {
|
||||||
throw `Cannot find texture named ${name}`;
|
throw new Error(`Cannot find texture named ${name}`);
|
||||||
}
|
}
|
||||||
if (width < 4) width = 4;
|
if (width < 4) width = 4;
|
||||||
if (height < 4) height = 4;
|
if (height < 4) height = 4;
|
||||||
|
Loading…
Reference in New Issue
Block a user