import { vec } from '../../../Math/vector';
import { parseImageFiltering } from '../../Filtering';
import { GraphicsDiagnostics } from '../../GraphicsDiagnostics';
import { ImageSourceAttributeConstants } from '../../ImageSource';
import { parseImageWrapping } from '../../Wrapping';
import { QuadIndexBuffer } from '../quad-index-buffer';
import { VertexBuffer } from '../vertex-buffer';
import { VertexLayout } from '../vertex-layout';
export class MaterialRenderer {
    constructor() {
        this.type = 'ex.material';
        this.priority = 0;
        this._textures = [];
    }
    initialize(gl, context) {
        this._gl = gl;
        this._context = context;
        // Setup memory layout
        this._buffer = new VertexBuffer({
            gl,
            size: 6 * 4, // 6 components * 4 verts
            type: 'dynamic'
        });
        // Setup a vertex layout/buffer to the material
        this._layout = new VertexLayout({
            gl,
            vertexBuffer: this._buffer,
            attributes: [
                ['a_position', 2],
                ['a_uv', 2],
                ['a_screenuv', 2]
            ],
            suppressWarnings: true
        });
        // Setup index buffer
        this._quads = new QuadIndexBuffer(gl, 1, true);
    }
    dispose() {
        this._buffer.dispose();
        this._quads.dispose();
        this._textures.length = 0;
        this._context = null;
        this._gl = null;
    }
    draw(image, sx, sy, swidth, sheight, dx, dy, dwidth, dheight) {
        var _a, _b, _c, _d;
        const gl = this._gl;
        // Extract context info
        const material = this._context.material;
        const transform = this._context.getTransform();
        const opacity = this._context.opacity;
        // material shader
        const shader = material.getShader();
        // construct geometry, or hold on to it in the material?
        // geometry primitive for drawing rectangles?
        // update data
        const vertexBuffer = this._layout.vertexBuffer.bufferData;
        let vertexIndex = 0;
        let width = (image === null || image === void 0 ? void 0 : image.width) || swidth || 0;
        let height = (image === null || image === void 0 ? void 0 : image.height) || sheight || 0;
        let view = [0, 0, (_a = swidth !== null && swidth !== void 0 ? swidth : image === null || image === void 0 ? void 0 : image.width) !== null && _a !== void 0 ? _a : 0, (_b = sheight !== null && sheight !== void 0 ? sheight : image === null || image === void 0 ? void 0 : image.height) !== null && _b !== void 0 ? _b : 0];
        let dest = [sx !== null && sx !== void 0 ? sx : 1, sy !== null && sy !== void 0 ? sy : 1];
        // If destination is specified, update view and dest
        if (dx !== undefined && dy !== undefined && dwidth !== undefined && dheight !== undefined) {
            view = [sx !== null && sx !== void 0 ? sx : 1, sy !== null && sy !== void 0 ? sy : 1, (_c = swidth !== null && swidth !== void 0 ? swidth : image === null || image === void 0 ? void 0 : image.width) !== null && _c !== void 0 ? _c : 0, (_d = sheight !== null && sheight !== void 0 ? sheight : image === null || image === void 0 ? void 0 : image.height) !== null && _d !== void 0 ? _d : 0];
            dest = [dx, dy];
            width = dwidth;
            height = dheight;
        }
        sx = view[0];
        sy = view[1];
        const sw = view[2];
        const sh = view[3];
        const topLeft = vec(dest[0], dest[1]);
        const topRight = vec(dest[0] + width, dest[1]);
        const bottomLeft = vec(dest[0], dest[1] + height);
        const bottomRight = vec(dest[0] + width, dest[1] + height);
        const imageWidth = image.width || width;
        const imageHeight = image.height || height;
        const uvx0 = sx / imageWidth;
        const uvy0 = sy / imageHeight;
        const uvx1 = (sx + sw - 0.01) / imageWidth;
        const uvy1 = (sy + sh - 0.01) / imageHeight;
        const topLeftScreen = transform.getPosition();
        const bottomRightScreen = topLeftScreen.add(bottomRight);
        const screenUVX0 = topLeftScreen.x / this._context.width;
        const screenUVY0 = topLeftScreen.y / this._context.height;
        const screenUVX1 = bottomRightScreen.x / this._context.width;
        const screenUVY1 = bottomRightScreen.y / this._context.height;
        // (0, 0) - 0
        vertexBuffer[vertexIndex++] = topLeft.x;
        vertexBuffer[vertexIndex++] = topLeft.y;
        vertexBuffer[vertexIndex++] = uvx0;
        vertexBuffer[vertexIndex++] = uvy0;
        vertexBuffer[vertexIndex++] = screenUVX0;
        vertexBuffer[vertexIndex++] = screenUVY0;
        // (0, 1) - 1
        vertexBuffer[vertexIndex++] = bottomLeft.x;
        vertexBuffer[vertexIndex++] = bottomLeft.y;
        vertexBuffer[vertexIndex++] = uvx0;
        vertexBuffer[vertexIndex++] = uvy1;
        vertexBuffer[vertexIndex++] = screenUVX0;
        vertexBuffer[vertexIndex++] = screenUVY1;
        // (1, 0) - 2
        vertexBuffer[vertexIndex++] = topRight.x;
        vertexBuffer[vertexIndex++] = topRight.y;
        vertexBuffer[vertexIndex++] = uvx1;
        vertexBuffer[vertexIndex++] = uvy0;
        vertexBuffer[vertexIndex++] = screenUVX1;
        vertexBuffer[vertexIndex++] = screenUVY0;
        // (1, 1) - 3
        vertexBuffer[vertexIndex++] = bottomRight.x;
        vertexBuffer[vertexIndex++] = bottomRight.y;
        vertexBuffer[vertexIndex++] = uvx1;
        vertexBuffer[vertexIndex++] = uvy1;
        vertexBuffer[vertexIndex++] = screenUVX1;
        vertexBuffer[vertexIndex++] = screenUVY1;
        // This creates and uploads the texture if not already done
        const texture = this._addImageAsTexture(image);
        // apply material
        material.use();
        this._layout.shader = shader;
        // apply layout and geometry
        this._layout.use(true);
        // apply time in ms since the page (performance.now())
        shader.trySetUniformFloat('u_time_ms', performance.now());
        // apply opacity
        shader.trySetUniformFloat('u_opacity', opacity);
        // apply resolution
        shader.trySetUniformFloatVector('u_resolution', vec(this._context.width, this._context.height));
        // apply graphic resolution
        shader.trySetUniformFloatVector('u_graphic_resolution', vec(imageWidth, imageHeight));
        // apply size
        shader.trySetUniformFloatVector('u_size', vec(sw, sh));
        // apply orthographic projection
        shader.trySetUniformMatrix('u_matrix', this._context.ortho);
        // apply geometry transform
        shader.trySetUniformMatrix('u_transform', transform.to4x4());
        // bind graphic image texture 'uniform sampler2D u_graphic;'
        gl.activeTexture(gl.TEXTURE0 + 0);
        gl.bindTexture(gl.TEXTURE_2D, texture);
        shader.trySetUniformInt('u_graphic', 0);
        // bind the screen texture
        gl.activeTexture(gl.TEXTURE0 + 1);
        gl.bindTexture(gl.TEXTURE_2D, this._context.materialScreenTexture);
        shader.trySetUniformInt('u_screen_texture', 1);
        // bind any additional textures in the material
        material.uploadAndBind(gl);
        // bind quad index buffer
        this._quads.bind();
        // Draw a single quad
        gl.drawElements(gl.TRIANGLES, 6, this._quads.bufferGlType, 0);
        GraphicsDiagnostics.DrawnImagesCount++;
        GraphicsDiagnostics.DrawCallCount++;
    }
    _addImageAsTexture(image) {
        const maybeFiltering = image.getAttribute(ImageSourceAttributeConstants.Filtering);
        const filtering = maybeFiltering ? parseImageFiltering(maybeFiltering) : null;
        const wrapX = parseImageWrapping(image.getAttribute(ImageSourceAttributeConstants.WrappingX));
        const wrapY = parseImageWrapping(image.getAttribute(ImageSourceAttributeConstants.WrappingY));
        const force = image.getAttribute('forceUpload') === 'true' ? true : false;
        const texture = this._context.textureLoader.load(image, {
            filtering,
            wrapping: { x: wrapX, y: wrapY }
        }, force);
        // remove force attribute after upload
        image.removeAttribute('forceUpload');
        if (this._textures.indexOf(texture) === -1) {
            this._textures.push(texture);
        }
        return texture;
    }
    hasPendingDraws() {
        return false;
    }
    flush() {
        // flush does not do anything, material renderer renders immediately per draw
    }
}
//# sourceMappingURL=material-renderer.js.map