diff --git a/packages/core/src/geometry/Buffer.js b/packages/core/src/geometry/Buffer.js index f43c42a..dd0e43c 100644 --- a/packages/core/src/geometry/Buffer.js +++ b/packages/core/src/geometry/Buffer.js @@ -1,3 +1,5 @@ +import Runner from 'mini-runner'; + let UID = 0; /* eslint-disable max-len */ @@ -11,6 +13,8 @@ { /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + * @param {boolean} [_static=true] `true` for static buffer + * @param {boolean} [index=false] `true` for index buffer */ constructor(data, _static = true, index = false) { @@ -36,6 +40,8 @@ this.static = _static; this.id = UID++; + + this.disposeRunner = new Runner('disposeBuffer', 2); } // TODO could explore flagging only a partial upload? @@ -49,14 +55,19 @@ } /** + * disposes WebGL resources that are connected to this geometry + */ + dispose() + { + this.disposeRunner.run(this, false); + } + + /** * Destroys the buffer */ destroy() { - for (let i = 0; i < this._glBuffers.length; i++) - { - this._glBuffers[i].destroy(); - } + this.dispose(); this.data = null; } diff --git a/packages/core/src/geometry/Buffer.js b/packages/core/src/geometry/Buffer.js index f43c42a..dd0e43c 100644 --- a/packages/core/src/geometry/Buffer.js +++ b/packages/core/src/geometry/Buffer.js @@ -1,3 +1,5 @@ +import Runner from 'mini-runner'; + let UID = 0; /* eslint-disable max-len */ @@ -11,6 +13,8 @@ { /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + * @param {boolean} [_static=true] `true` for static buffer + * @param {boolean} [index=false] `true` for index buffer */ constructor(data, _static = true, index = false) { @@ -36,6 +40,8 @@ this.static = _static; this.id = UID++; + + this.disposeRunner = new Runner('disposeBuffer', 2); } // TODO could explore flagging only a partial upload? @@ -49,14 +55,19 @@ } /** + * disposes WebGL resources that are connected to this geometry + */ + dispose() + { + this.disposeRunner.run(this, false); + } + + /** * Destroys the buffer */ destroy() { - for (let i = 0; i < this._glBuffers.length; i++) - { - this._glBuffers[i].destroy(); - } + this.dispose(); this.data = null; } diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js index 0477e8a..2b6ea1f 100644 --- a/packages/core/src/geometry/GLBuffer.js +++ b/packages/core/src/geometry/GLBuffer.js @@ -5,5 +5,6 @@ this.buffer = buffer; this.updateID = -1; this.byteLength = -1; + this.refCount = 0; } } diff --git a/packages/core/src/geometry/Buffer.js b/packages/core/src/geometry/Buffer.js index f43c42a..dd0e43c 100644 --- a/packages/core/src/geometry/Buffer.js +++ b/packages/core/src/geometry/Buffer.js @@ -1,3 +1,5 @@ +import Runner from 'mini-runner'; + let UID = 0; /* eslint-disable max-len */ @@ -11,6 +13,8 @@ { /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + * @param {boolean} [_static=true] `true` for static buffer + * @param {boolean} [index=false] `true` for index buffer */ constructor(data, _static = true, index = false) { @@ -36,6 +40,8 @@ this.static = _static; this.id = UID++; + + this.disposeRunner = new Runner('disposeBuffer', 2); } // TODO could explore flagging only a partial upload? @@ -49,14 +55,19 @@ } /** + * disposes WebGL resources that are connected to this geometry + */ + dispose() + { + this.disposeRunner.run(this, false); + } + + /** * Destroys the buffer */ destroy() { - for (let i = 0; i < this._glBuffers.length; i++) - { - this._glBuffers[i].destroy(); - } + this.dispose(); this.data = null; } diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js index 0477e8a..2b6ea1f 100644 --- a/packages/core/src/geometry/GLBuffer.js +++ b/packages/core/src/geometry/GLBuffer.js @@ -5,5 +5,6 @@ this.buffer = buffer; this.updateID = -1; this.byteLength = -1; + this.refCount = 0; } } diff --git a/packages/core/src/geometry/Geometry.js b/packages/core/src/geometry/Geometry.js index ed29751..5e5bc73 100644 --- a/packages/core/src/geometry/Geometry.js +++ b/packages/core/src/geometry/Geometry.js @@ -2,6 +2,7 @@ import Buffer from './Buffer'; import interleaveTypedArrays from './utils/interleaveTypedArrays'; import getBufferType from './utils/getBufferType'; +import Runner from 'mini-runner'; const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; let UID = 0; @@ -54,7 +55,7 @@ * A map of renderer IDs to webgl VAOs * * @protected - * @type {Array} + * @type {object} */ this.glVertexArrayObjects = {}; @@ -65,6 +66,14 @@ this.instanceCount = 1; this._size = null; + + this.disposeRunner = new Runner('disposeGeometry', 2); + + /** + * Count of existing (not destroyed) meshes that reference this geometry + * @member {boolean} + */ + this.refCount = 0; } /** @@ -245,21 +254,19 @@ } /** + * disposes WebGL resources that are connected to this geometry + */ + dispose() + { + this.disposeRunner.run(this, false); + } + + /** * Destroys the geometry. */ destroy() { - for (let i = 0; i < this.glVertexArrayObjects.length; i++) - { - this.glVertexArrayObjects[i].destroy(); - } - - this.glVertexArrayObjects = null; - - for (let i = 0; i < this.buffers.length; i++) - { - this.buffers[i].destroy(); - } + this.dispose(); this.buffers = null; this.indexBuffer.destroy(); diff --git a/packages/core/src/geometry/Buffer.js b/packages/core/src/geometry/Buffer.js index f43c42a..dd0e43c 100644 --- a/packages/core/src/geometry/Buffer.js +++ b/packages/core/src/geometry/Buffer.js @@ -1,3 +1,5 @@ +import Runner from 'mini-runner'; + let UID = 0; /* eslint-disable max-len */ @@ -11,6 +13,8 @@ { /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + * @param {boolean} [_static=true] `true` for static buffer + * @param {boolean} [index=false] `true` for index buffer */ constructor(data, _static = true, index = false) { @@ -36,6 +40,8 @@ this.static = _static; this.id = UID++; + + this.disposeRunner = new Runner('disposeBuffer', 2); } // TODO could explore flagging only a partial upload? @@ -49,14 +55,19 @@ } /** + * disposes WebGL resources that are connected to this geometry + */ + dispose() + { + this.disposeRunner.run(this, false); + } + + /** * Destroys the buffer */ destroy() { - for (let i = 0; i < this._glBuffers.length; i++) - { - this._glBuffers[i].destroy(); - } + this.dispose(); this.data = null; } diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js index 0477e8a..2b6ea1f 100644 --- a/packages/core/src/geometry/GLBuffer.js +++ b/packages/core/src/geometry/GLBuffer.js @@ -5,5 +5,6 @@ this.buffer = buffer; this.updateID = -1; this.byteLength = -1; + this.refCount = 0; } } diff --git a/packages/core/src/geometry/Geometry.js b/packages/core/src/geometry/Geometry.js index ed29751..5e5bc73 100644 --- a/packages/core/src/geometry/Geometry.js +++ b/packages/core/src/geometry/Geometry.js @@ -2,6 +2,7 @@ import Buffer from './Buffer'; import interleaveTypedArrays from './utils/interleaveTypedArrays'; import getBufferType from './utils/getBufferType'; +import Runner from 'mini-runner'; const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; let UID = 0; @@ -54,7 +55,7 @@ * A map of renderer IDs to webgl VAOs * * @protected - * @type {Array} + * @type {object} */ this.glVertexArrayObjects = {}; @@ -65,6 +66,14 @@ this.instanceCount = 1; this._size = null; + + this.disposeRunner = new Runner('disposeGeometry', 2); + + /** + * Count of existing (not destroyed) meshes that reference this geometry + * @member {boolean} + */ + this.refCount = 0; } /** @@ -245,21 +254,19 @@ } /** + * disposes WebGL resources that are connected to this geometry + */ + dispose() + { + this.disposeRunner.run(this, false); + } + + /** * Destroys the geometry. */ destroy() { - for (let i = 0; i < this.glVertexArrayObjects.length; i++) - { - this.glVertexArrayObjects[i].destroy(); - } - - this.glVertexArrayObjects = null; - - for (let i = 0; i < this.buffers.length; i++) - { - this.buffers[i].destroy(); - } + this.dispose(); this.buffers = null; this.indexBuffer.destroy(); diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js index a5b5cd3..b2a3ad0 100644 --- a/packages/core/src/geometry/GeometrySystem.js +++ b/packages/core/src/geometry/GeometrySystem.js @@ -45,6 +45,20 @@ * @readonly */ this.boundBuffers = {}; + + /** + * Cache for all geometries by id, used in case renderer gets destroyed or for profiling + * @member {object} + * @readonly + */ + this.managedGeometries = {}; + + /** + * Cache for all buffers by id, used in case renderer gets destroyed or for profiling + * @member {object} + * @readonly + */ + this.managedBuffers = {}; } /** @@ -52,6 +66,8 @@ */ contextChange() { + this.disposeAll(true); + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; @@ -140,6 +156,8 @@ if (!vaos) { + this.managedGeometries[geometry.id] = geometry; + geometry.disposeRunner.add(this); geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; } @@ -248,7 +266,7 @@ * Takes a geometry and program and generates a unique signature for them. * * @param {PIXI.Geometry} geometry to get signature from - * @param {PIXI.Program} prgram to test geometry against + * @param {PIXI.Program} program to test geometry against * @returns {String} Unique signature of the geometry and program * @protected */ @@ -257,7 +275,7 @@ const attribs = geometry.attributes; const shaderAttributes = program.attributeData; - const strings = [geometry.id]; + const strings = ['g', geometry.id]; for (const i in attribs) { @@ -360,7 +378,11 @@ if (!buffer._glBuffers[CONTEXT_UID]) { buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + this.managedBuffers[buffer.id] = buffer; + buffer.disposeRunner.add(this); } + + buffer._glBuffers[CONTEXT_UID].refCount++; } // TODO - maybe make this a data object? @@ -378,6 +400,108 @@ } /** + * Disposes buffer + * @param {PIXI.Buffer} buffer buffer with data + * @param {boolean} [contextLost=false] If context was lost, we suppress deleteVertexArray + */ + disposeBuffer(buffer, contextLost) + { + if (!this.managedBuffers[buffer.id]) + { + return; + } + + delete this.managedBuffers[buffer.id]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + const gl = this.gl; + + buffer.disposeRunner.remove(this); + + if (!glBuffer) + { + return; + } + + if (!contextLost) + { + gl.deleteBuffer(glBuffer.buffer); + } + + delete buffer._glBuffers[this.CONTEXT_UID]; + } + + /** + * Disposes geometry + * @param {PIXI.Geometry} geometry Geometry with buffers. Only VAO will be disposed + * @param {boolean} [contextLost=false] If context was lost, we suppress deleteVertexArray + */ + disposeGeometry(geometry, contextLost) + { + if (!this.managedGeometries[geometry.id]) + { + return; + } + + delete this.managedGeometries[geometry.id]; + + const vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + const gl = this.gl; + const buffers = geometry.buffers; + + geometry.disposeRunner.remove(this); + + if (!vaos) + { + return; + } + + for (let i = 0; i < buffers.length; i++) + { + const buf = buffers[i]._glBuffers[this.CONTEXT_UID]; + + buf.refCount--; + if (buf.refCount === 0 && !contextLost) + { + this.disposeBuffer(buffers[i], contextLost); + } + } + + if (!contextLost) + { + for (const vaoId in vaos) + { + // delete only signatures, everything else are copies + if (vaoId[0] === 'g') + { + gl.deleteVertexArray(vaos[vaoId]); + } + } + } + + delete geometry.glVertexArrayObjects[this.CONTEXT_UID]; + } + + /** + * dispose all WebGL resources of all managed geometries and buffers + * @param {boolean} [contextLost=false] If context was lost, we suppress `gl.delete` calls + */ + disposeAll(contextLost) + { + let all = Object.keys(this.managedGeometries); + + for (let i = 0; i < all.length; i++) + { + this.disposeGeometry(this.managedGeometries[all[i]], contextLost); + } + all = Object.keys(this.managedBuffers); + for (let i = 0; i < all.length; i++) + { + this.disposeBuffer(this.managedBuffers[all[i]], contextLost); + } + } + + /** * Activate vertex array object * * @protected diff --git a/packages/core/src/geometry/Buffer.js b/packages/core/src/geometry/Buffer.js index f43c42a..dd0e43c 100644 --- a/packages/core/src/geometry/Buffer.js +++ b/packages/core/src/geometry/Buffer.js @@ -1,3 +1,5 @@ +import Runner from 'mini-runner'; + let UID = 0; /* eslint-disable max-len */ @@ -11,6 +13,8 @@ { /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + * @param {boolean} [_static=true] `true` for static buffer + * @param {boolean} [index=false] `true` for index buffer */ constructor(data, _static = true, index = false) { @@ -36,6 +40,8 @@ this.static = _static; this.id = UID++; + + this.disposeRunner = new Runner('disposeBuffer', 2); } // TODO could explore flagging only a partial upload? @@ -49,14 +55,19 @@ } /** + * disposes WebGL resources that are connected to this geometry + */ + dispose() + { + this.disposeRunner.run(this, false); + } + + /** * Destroys the buffer */ destroy() { - for (let i = 0; i < this._glBuffers.length; i++) - { - this._glBuffers[i].destroy(); - } + this.dispose(); this.data = null; } diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js index 0477e8a..2b6ea1f 100644 --- a/packages/core/src/geometry/GLBuffer.js +++ b/packages/core/src/geometry/GLBuffer.js @@ -5,5 +5,6 @@ this.buffer = buffer; this.updateID = -1; this.byteLength = -1; + this.refCount = 0; } } diff --git a/packages/core/src/geometry/Geometry.js b/packages/core/src/geometry/Geometry.js index ed29751..5e5bc73 100644 --- a/packages/core/src/geometry/Geometry.js +++ b/packages/core/src/geometry/Geometry.js @@ -2,6 +2,7 @@ import Buffer from './Buffer'; import interleaveTypedArrays from './utils/interleaveTypedArrays'; import getBufferType from './utils/getBufferType'; +import Runner from 'mini-runner'; const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; let UID = 0; @@ -54,7 +55,7 @@ * A map of renderer IDs to webgl VAOs * * @protected - * @type {Array} + * @type {object} */ this.glVertexArrayObjects = {}; @@ -65,6 +66,14 @@ this.instanceCount = 1; this._size = null; + + this.disposeRunner = new Runner('disposeGeometry', 2); + + /** + * Count of existing (not destroyed) meshes that reference this geometry + * @member {boolean} + */ + this.refCount = 0; } /** @@ -245,21 +254,19 @@ } /** + * disposes WebGL resources that are connected to this geometry + */ + dispose() + { + this.disposeRunner.run(this, false); + } + + /** * Destroys the geometry. */ destroy() { - for (let i = 0; i < this.glVertexArrayObjects.length; i++) - { - this.glVertexArrayObjects[i].destroy(); - } - - this.glVertexArrayObjects = null; - - for (let i = 0; i < this.buffers.length; i++) - { - this.buffers[i].destroy(); - } + this.dispose(); this.buffers = null; this.indexBuffer.destroy(); diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js index a5b5cd3..b2a3ad0 100644 --- a/packages/core/src/geometry/GeometrySystem.js +++ b/packages/core/src/geometry/GeometrySystem.js @@ -45,6 +45,20 @@ * @readonly */ this.boundBuffers = {}; + + /** + * Cache for all geometries by id, used in case renderer gets destroyed or for profiling + * @member {object} + * @readonly + */ + this.managedGeometries = {}; + + /** + * Cache for all buffers by id, used in case renderer gets destroyed or for profiling + * @member {object} + * @readonly + */ + this.managedBuffers = {}; } /** @@ -52,6 +66,8 @@ */ contextChange() { + this.disposeAll(true); + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; @@ -140,6 +156,8 @@ if (!vaos) { + this.managedGeometries[geometry.id] = geometry; + geometry.disposeRunner.add(this); geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; } @@ -248,7 +266,7 @@ * Takes a geometry and program and generates a unique signature for them. * * @param {PIXI.Geometry} geometry to get signature from - * @param {PIXI.Program} prgram to test geometry against + * @param {PIXI.Program} program to test geometry against * @returns {String} Unique signature of the geometry and program * @protected */ @@ -257,7 +275,7 @@ const attribs = geometry.attributes; const shaderAttributes = program.attributeData; - const strings = [geometry.id]; + const strings = ['g', geometry.id]; for (const i in attribs) { @@ -360,7 +378,11 @@ if (!buffer._glBuffers[CONTEXT_UID]) { buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + this.managedBuffers[buffer.id] = buffer; + buffer.disposeRunner.add(this); } + + buffer._glBuffers[CONTEXT_UID].refCount++; } // TODO - maybe make this a data object? @@ -378,6 +400,108 @@ } /** + * Disposes buffer + * @param {PIXI.Buffer} buffer buffer with data + * @param {boolean} [contextLost=false] If context was lost, we suppress deleteVertexArray + */ + disposeBuffer(buffer, contextLost) + { + if (!this.managedBuffers[buffer.id]) + { + return; + } + + delete this.managedBuffers[buffer.id]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + const gl = this.gl; + + buffer.disposeRunner.remove(this); + + if (!glBuffer) + { + return; + } + + if (!contextLost) + { + gl.deleteBuffer(glBuffer.buffer); + } + + delete buffer._glBuffers[this.CONTEXT_UID]; + } + + /** + * Disposes geometry + * @param {PIXI.Geometry} geometry Geometry with buffers. Only VAO will be disposed + * @param {boolean} [contextLost=false] If context was lost, we suppress deleteVertexArray + */ + disposeGeometry(geometry, contextLost) + { + if (!this.managedGeometries[geometry.id]) + { + return; + } + + delete this.managedGeometries[geometry.id]; + + const vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + const gl = this.gl; + const buffers = geometry.buffers; + + geometry.disposeRunner.remove(this); + + if (!vaos) + { + return; + } + + for (let i = 0; i < buffers.length; i++) + { + const buf = buffers[i]._glBuffers[this.CONTEXT_UID]; + + buf.refCount--; + if (buf.refCount === 0 && !contextLost) + { + this.disposeBuffer(buffers[i], contextLost); + } + } + + if (!contextLost) + { + for (const vaoId in vaos) + { + // delete only signatures, everything else are copies + if (vaoId[0] === 'g') + { + gl.deleteVertexArray(vaos[vaoId]); + } + } + } + + delete geometry.glVertexArrayObjects[this.CONTEXT_UID]; + } + + /** + * dispose all WebGL resources of all managed geometries and buffers + * @param {boolean} [contextLost=false] If context was lost, we suppress `gl.delete` calls + */ + disposeAll(contextLost) + { + let all = Object.keys(this.managedGeometries); + + for (let i = 0; i < all.length; i++) + { + this.disposeGeometry(this.managedGeometries[all[i]], contextLost); + } + all = Object.keys(this.managedBuffers); + for (let i = 0; i < all.length; i++) + { + this.disposeBuffer(this.managedBuffers[all[i]], contextLost); + } + } + + /** * Activate vertex array object * * @protected diff --git a/packages/graphics/src/Graphics.js b/packages/graphics/src/Graphics.js index 4cf6c13..de4dd3d 100644 --- a/packages/graphics/src/Graphics.js +++ b/packages/graphics/src/Graphics.js @@ -51,9 +51,12 @@ * custom attributes within buffers, reducing the cost of passing all * this data to the GPU. Can be shared between multiple Mesh or Graphics objects. * @member {PIXI.Geometry} + * @readonly */ this.geometry = geometry || new GraphicsGeometry(); + this.geometry.refCount++; + /** * Represents the vertex and fragment shaders that processes the geometry and runs on the GPU. * Can be shared between multiple Graphics objects. @@ -69,14 +72,6 @@ this.state = State.for2d(); /** - * If this Graphics object owns the GraphicsGeometry - * - * @member {boolean} - * @protected - */ - this._ownsGeometry = geometry === null; - - /** * Current fill style * * @member {PIXI.FillStyle} @@ -1110,16 +1105,15 @@ * Should it destroy the texture of the child sprite * @param {boolean} [options.baseTexture=false] - Only used for child Sprites if options.children is set to true * Should it destroy the base texture of the child sprite - * @param {boolean} [options.geometry=false] - if set to true, the geometry object will be - * be destroyed. */ destroy(options) { - const destroyGeometry = typeof options === 'boolean' ? options : options && options.geometry; + super.destroy(options); - if (destroyGeometry || this._ownsGeometry) + this.geometry.refCount--; + if (this.geometry.refCount === 0) { - this.geometry.destroy(); + this.geometry.dispose(); } this._matrix = null; diff --git a/packages/core/src/geometry/Buffer.js b/packages/core/src/geometry/Buffer.js index f43c42a..dd0e43c 100644 --- a/packages/core/src/geometry/Buffer.js +++ b/packages/core/src/geometry/Buffer.js @@ -1,3 +1,5 @@ +import Runner from 'mini-runner'; + let UID = 0; /* eslint-disable max-len */ @@ -11,6 +13,8 @@ { /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + * @param {boolean} [_static=true] `true` for static buffer + * @param {boolean} [index=false] `true` for index buffer */ constructor(data, _static = true, index = false) { @@ -36,6 +40,8 @@ this.static = _static; this.id = UID++; + + this.disposeRunner = new Runner('disposeBuffer', 2); } // TODO could explore flagging only a partial upload? @@ -49,14 +55,19 @@ } /** + * disposes WebGL resources that are connected to this geometry + */ + dispose() + { + this.disposeRunner.run(this, false); + } + + /** * Destroys the buffer */ destroy() { - for (let i = 0; i < this._glBuffers.length; i++) - { - this._glBuffers[i].destroy(); - } + this.dispose(); this.data = null; } diff --git a/packages/core/src/geometry/GLBuffer.js b/packages/core/src/geometry/GLBuffer.js index 0477e8a..2b6ea1f 100644 --- a/packages/core/src/geometry/GLBuffer.js +++ b/packages/core/src/geometry/GLBuffer.js @@ -5,5 +5,6 @@ this.buffer = buffer; this.updateID = -1; this.byteLength = -1; + this.refCount = 0; } } diff --git a/packages/core/src/geometry/Geometry.js b/packages/core/src/geometry/Geometry.js index ed29751..5e5bc73 100644 --- a/packages/core/src/geometry/Geometry.js +++ b/packages/core/src/geometry/Geometry.js @@ -2,6 +2,7 @@ import Buffer from './Buffer'; import interleaveTypedArrays from './utils/interleaveTypedArrays'; import getBufferType from './utils/getBufferType'; +import Runner from 'mini-runner'; const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; let UID = 0; @@ -54,7 +55,7 @@ * A map of renderer IDs to webgl VAOs * * @protected - * @type {Array} + * @type {object} */ this.glVertexArrayObjects = {}; @@ -65,6 +66,14 @@ this.instanceCount = 1; this._size = null; + + this.disposeRunner = new Runner('disposeGeometry', 2); + + /** + * Count of existing (not destroyed) meshes that reference this geometry + * @member {boolean} + */ + this.refCount = 0; } /** @@ -245,21 +254,19 @@ } /** + * disposes WebGL resources that are connected to this geometry + */ + dispose() + { + this.disposeRunner.run(this, false); + } + + /** * Destroys the geometry. */ destroy() { - for (let i = 0; i < this.glVertexArrayObjects.length; i++) - { - this.glVertexArrayObjects[i].destroy(); - } - - this.glVertexArrayObjects = null; - - for (let i = 0; i < this.buffers.length; i++) - { - this.buffers[i].destroy(); - } + this.dispose(); this.buffers = null; this.indexBuffer.destroy(); diff --git a/packages/core/src/geometry/GeometrySystem.js b/packages/core/src/geometry/GeometrySystem.js index a5b5cd3..b2a3ad0 100644 --- a/packages/core/src/geometry/GeometrySystem.js +++ b/packages/core/src/geometry/GeometrySystem.js @@ -45,6 +45,20 @@ * @readonly */ this.boundBuffers = {}; + + /** + * Cache for all geometries by id, used in case renderer gets destroyed or for profiling + * @member {object} + * @readonly + */ + this.managedGeometries = {}; + + /** + * Cache for all buffers by id, used in case renderer gets destroyed or for profiling + * @member {object} + * @readonly + */ + this.managedBuffers = {}; } /** @@ -52,6 +66,8 @@ */ contextChange() { + this.disposeAll(true); + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; @@ -140,6 +156,8 @@ if (!vaos) { + this.managedGeometries[geometry.id] = geometry; + geometry.disposeRunner.add(this); geometry.glVertexArrayObjects[this.CONTEXT_UID] = vaos = {}; } @@ -248,7 +266,7 @@ * Takes a geometry and program and generates a unique signature for them. * * @param {PIXI.Geometry} geometry to get signature from - * @param {PIXI.Program} prgram to test geometry against + * @param {PIXI.Program} program to test geometry against * @returns {String} Unique signature of the geometry and program * @protected */ @@ -257,7 +275,7 @@ const attribs = geometry.attributes; const shaderAttributes = program.attributeData; - const strings = [geometry.id]; + const strings = ['g', geometry.id]; for (const i in attribs) { @@ -360,7 +378,11 @@ if (!buffer._glBuffers[CONTEXT_UID]) { buffer._glBuffers[CONTEXT_UID] = new GLBuffer(gl.createBuffer()); + this.managedBuffers[buffer.id] = buffer; + buffer.disposeRunner.add(this); } + + buffer._glBuffers[CONTEXT_UID].refCount++; } // TODO - maybe make this a data object? @@ -378,6 +400,108 @@ } /** + * Disposes buffer + * @param {PIXI.Buffer} buffer buffer with data + * @param {boolean} [contextLost=false] If context was lost, we suppress deleteVertexArray + */ + disposeBuffer(buffer, contextLost) + { + if (!this.managedBuffers[buffer.id]) + { + return; + } + + delete this.managedBuffers[buffer.id]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + const gl = this.gl; + + buffer.disposeRunner.remove(this); + + if (!glBuffer) + { + return; + } + + if (!contextLost) + { + gl.deleteBuffer(glBuffer.buffer); + } + + delete buffer._glBuffers[this.CONTEXT_UID]; + } + + /** + * Disposes geometry + * @param {PIXI.Geometry} geometry Geometry with buffers. Only VAO will be disposed + * @param {boolean} [contextLost=false] If context was lost, we suppress deleteVertexArray + */ + disposeGeometry(geometry, contextLost) + { + if (!this.managedGeometries[geometry.id]) + { + return; + } + + delete this.managedGeometries[geometry.id]; + + const vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + const gl = this.gl; + const buffers = geometry.buffers; + + geometry.disposeRunner.remove(this); + + if (!vaos) + { + return; + } + + for (let i = 0; i < buffers.length; i++) + { + const buf = buffers[i]._glBuffers[this.CONTEXT_UID]; + + buf.refCount--; + if (buf.refCount === 0 && !contextLost) + { + this.disposeBuffer(buffers[i], contextLost); + } + } + + if (!contextLost) + { + for (const vaoId in vaos) + { + // delete only signatures, everything else are copies + if (vaoId[0] === 'g') + { + gl.deleteVertexArray(vaos[vaoId]); + } + } + } + + delete geometry.glVertexArrayObjects[this.CONTEXT_UID]; + } + + /** + * dispose all WebGL resources of all managed geometries and buffers + * @param {boolean} [contextLost=false] If context was lost, we suppress `gl.delete` calls + */ + disposeAll(contextLost) + { + let all = Object.keys(this.managedGeometries); + + for (let i = 0; i < all.length; i++) + { + this.disposeGeometry(this.managedGeometries[all[i]], contextLost); + } + all = Object.keys(this.managedBuffers); + for (let i = 0; i < all.length; i++) + { + this.disposeBuffer(this.managedBuffers[all[i]], contextLost); + } + } + + /** * Activate vertex array object * * @protected diff --git a/packages/graphics/src/Graphics.js b/packages/graphics/src/Graphics.js index 4cf6c13..de4dd3d 100644 --- a/packages/graphics/src/Graphics.js +++ b/packages/graphics/src/Graphics.js @@ -51,9 +51,12 @@ * custom attributes within buffers, reducing the cost of passing all * this data to the GPU. Can be shared between multiple Mesh or Graphics objects. * @member {PIXI.Geometry} + * @readonly */ this.geometry = geometry || new GraphicsGeometry(); + this.geometry.refCount++; + /** * Represents the vertex and fragment shaders that processes the geometry and runs on the GPU. * Can be shared between multiple Graphics objects. @@ -69,14 +72,6 @@ this.state = State.for2d(); /** - * If this Graphics object owns the GraphicsGeometry - * - * @member {boolean} - * @protected - */ - this._ownsGeometry = geometry === null; - - /** * Current fill style * * @member {PIXI.FillStyle} @@ -1110,16 +1105,15 @@ * Should it destroy the texture of the child sprite * @param {boolean} [options.baseTexture=false] - Only used for child Sprites if options.children is set to true * Should it destroy the base texture of the child sprite - * @param {boolean} [options.geometry=false] - if set to true, the geometry object will be - * be destroyed. */ destroy(options) { - const destroyGeometry = typeof options === 'boolean' ? options : options && options.geometry; + super.destroy(options); - if (destroyGeometry || this._ownsGeometry) + this.geometry.refCount--; + if (this.geometry.refCount === 0) { - this.geometry.destroy(); + this.geometry.dispose(); } this._matrix = null; diff --git a/packages/mesh/src/Mesh.js b/packages/mesh/src/Mesh.js index f88f0fc..b288c58 100644 --- a/packages/mesh/src/Mesh.js +++ b/packages/mesh/src/Mesh.js @@ -44,9 +44,12 @@ * custom attributes within buffers, reducing the cost of passing all * this data to the GPU. Can be shared between multiple Mesh objects. * @member {PIXI.Geometry} + * @readonly */ this.geometry = geometry; + geometry.refCount++; + /** * Represents the vertex and fragment shaders that processes the geometry and runs on the GPU. * Can be shared between multiple Mesh objects. @@ -465,6 +468,12 @@ { super.destroy(options); + this.geometry.refCount--; + if (this.geometry.refCount === 0) + { + this.geometry.dispose(); + } + this.geometry = null; this.shader = null; this.state = null;