Самый быстрый способ может зависеть от графического процессора и множества других факторов, таких как то, как вы рисуете круги, 2D, 3D, смешиваете ли вы их, используете ли вы z-буфер и т. Д. ... но в целом,меньше треугольников быстрее, чем больше, и меньше пикселей быстрее, чем больше. Итак, все, что мы действительно можем сделать, это попробовать.
Сначала давайте просто нарисуем текстурированные квадраты без смешивания. Во-первых, мне всегда кажется, что от WebGL получаются противоречивые результаты, но в моих тестах на моем GPU я получаю квады по 20k-30k при 60 кадрах в секунду на этом холсте 300x150 с использованием экземпляров
function main() {
const gl = document.querySelector('canvas').getContext('webgl');
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext) {
return alert('need ANGLE_instanced_arrays');
}
twgl.addExtensionsToContext(gl);
const vs = `
attribute float id;
attribute vec4 position;
attribute vec2 texcoord;
uniform float time;
varying vec2 v_texcoord;
varying vec4 v_color;
void main() {
float o = id + time;
gl_Position = position + vec4(
vec2(
fract(o * 0.1373),
fract(o * 0.5127)) * 2.0 - 1.0,
0, 0);
v_texcoord = texcoord;
v_color = vec4(fract(vec3(id) * vec3(0.127, 0.373, 0.513)), 1);
}`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
varying vec4 v_color;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, v_texcoord) * v_color;
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const maxCount = 250000;
const ids = new Float32Array(maxCount);
for (let i = 0; i < ids.length; ++i) {
ids[i] = i;
}
const x = 16 / 300 * 2;
const y = 16 / 150 * 2;
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: {
numComponents: 2,
data: [
-x, -y,
x, -y,
-x, y,
-x, y,
x, -y,
x, y,
],
},
texcoord: [
0, 1,
1, 1,
0, 0,
0, 0,
1, 1,
1, 0,
],
id: {
numComponents: 1,
data: ids,
divisor: 1,
}
});
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
{
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = 32;
ctx.canvas.height = 32;
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(16, 16, 15, 0, Math.PI * 2);
ctx.fill();
const tex = twgl.createTexture(gl, { src: ctx.canvas });
}
const fpsElem = document.querySelector('#fps');
const countElem = document.querySelector('#count');
let count;
function getCount() {
count = Math.min(maxCount, parseInt(countElem.value));
}
countElem.addEventListener('input', getCount);
getCount();
const maxHistory = 60;
const fpsHistory = new Array(maxHistory).fill(0);
let historyNdx = 0;
let historyTotal = 0;
let then = 0;
function render(now) {
const deltaTime = now - then;
then = now;
historyTotal += deltaTime - fpsHistory[historyNdx];
fpsHistory[historyNdx] = deltaTime;
historyNdx = (historyNdx + 1) % maxHistory;
fpsElem.textContent = (1000 / (historyTotal / maxHistory)).toFixed(1);
gl.useProgram(programInfo.program);
twgl.setUniforms(programInfo, {time: now * 0.001});
ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 6, count);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
canvas { display: block; border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>
<div>fps: <span id="fps"></span></div>
<div>count: <input type="number" id="count" min="0" max="1000000" value="25000"></div>
И я получаю ту же скорость на 60 кадрах в секунду, используя повторение в геометрию вместо создания экземпляров. Это удивляет меня, потому что 7-8 лет назад, когда я тестировал повторную геометрию, был на 20-30% быстрее. Будь то из-за того, что у меня сейчас лучший графический процессор, или из-за лучшего драйвера, или из-за того, что я понятия не имею.
function main() {
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute float id;
attribute vec4 position;
attribute vec2 texcoord;
uniform float time;
varying vec2 v_texcoord;
varying vec4 v_color;
void main() {
float o = id + time;
gl_Position = position + vec4(
vec2(
fract(o * 0.1373),
fract(o * 0.5127)) * 2.0 - 1.0,
0, 0);
v_texcoord = texcoord;
v_color = vec4(fract(vec3(id) * vec3(0.127, 0.373, 0.513)), 1);
}`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
varying vec4 v_color;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, v_texcoord) * v_color;
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const maxCount = 250000;
const x = 16 / 300 * 2;
const y = 16 / 150 * 2;
const quadPositions = [
-x, -y,
x, -y,
-x, y,
-x, y,
x, -y,
x, y,
];
const quadTexcoords = [
0, 1,
1, 1,
0, 0,
0, 0,
1, 1,
1, 0,
];
const positions = new Float32Array(maxCount * 2 * 6);
const texcoords = new Float32Array(maxCount * 2 * 6);
for (let i = 0; i < maxCount; ++i) {
const off = i * 2 * 6;
positions.set(quadPositions, off);
texcoords.set(quadTexcoords, off);
}
const ids = new Float32Array(maxCount * 6);
for (let i = 0; i < ids.length; ++i) {
ids[i] = i / 6 | 0;
}
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: {
numComponents: 2,
data: positions,
},
texcoord: texcoords,
id: {
numComponents: 1,
data: ids,
}
});
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
{
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = 32;
ctx.canvas.height = 32;
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(16, 16, 15, 0, Math.PI * 2);
ctx.fill();
const tex = twgl.createTexture(gl, { src: ctx.canvas });
}
const fpsElem = document.querySelector('#fps');
const countElem = document.querySelector('#count');
let count;
function getCount() {
count = Math.min(maxCount, parseInt(countElem.value));
}
countElem.addEventListener('input', getCount);
getCount();
const maxHistory = 60;
const fpsHistory = new Array(maxHistory).fill(0);
let historyNdx = 0;
let historyTotal = 0;
let then = 0;
function render(now) {
const deltaTime = now - then;
then = now;
historyTotal += deltaTime - fpsHistory[historyNdx];
fpsHistory[historyNdx] = deltaTime;
historyNdx = (historyNdx + 1) % maxHistory;
fpsElem.textContent = (1000 / (historyTotal / maxHistory)).toFixed(1);
gl.useProgram(programInfo.program);
twgl.setUniforms(programInfo, {time: now * 0.001});
gl.drawArrays(gl.TRIANGLES, 0, 6 * count);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
canvas { display: block; border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>
<div>fps: <span id="fps"></span></div>
<div>count: <input type="number" id="count" min="0" max="1000000" value="25000"></div>
Следующим шагом будут текстуры или вычисление круга в фрагментном шейдере.
function main() {
const gl = document.querySelector('canvas').getContext('webgl');
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext) {
return alert('need ANGLE_instanced_arrays');
}
twgl.addExtensionsToContext(gl);
const vs = `
attribute float id;
attribute vec4 position;
attribute vec2 texcoord;
uniform float time;
varying vec2 v_texcoord;
varying vec4 v_color;
void main() {
float o = id + time;
gl_Position = position + vec4(
vec2(
fract(o * 0.1373),
fract(o * 0.5127)) * 2.0 - 1.0,
0, 0);
v_texcoord = texcoord;
v_color = vec4(fract(vec3(id) * vec3(0.127, 0.373, 0.513)), 1);
}`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
varying vec4 v_color;
void main() {
gl_FragColor = mix(
v_color,
vec4(0),
step(1.0, length(v_texcoord.xy * 2. - 1.)));
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const maxCount = 250000;
const ids = new Float32Array(maxCount);
for (let i = 0; i < ids.length; ++i) {
ids[i] = i;
}
const x = 16 / 300 * 2;
const y = 16 / 150 * 2;
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: {
numComponents: 2,
data: [
-x, -y,
x, -y,
-x, y,
-x, y,
x, -y,
x, y,
],
},
texcoord: [
0, 1,
1, 1,
0, 0,
0, 0,
1, 1,
1, 0,
],
id: {
numComponents: 1,
data: ids,
divisor: 1,
}
});
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
const fpsElem = document.querySelector('#fps');
const countElem = document.querySelector('#count');
let count;
function getCount() {
count = Math.min(maxCount, parseInt(countElem.value));
}
countElem.addEventListener('input', getCount);
getCount();
const maxHistory = 60;
const fpsHistory = new Array(maxHistory).fill(0);
let historyNdx = 0;
let historyTotal = 0;
let then = 0;
function render(now) {
const deltaTime = now - then;
then = now;
historyTotal += deltaTime - fpsHistory[historyNdx];
fpsHistory[historyNdx] = deltaTime;
historyNdx = (historyNdx + 1) % maxHistory;
fpsElem.textContent = (1000 / (historyTotal / maxHistory)).toFixed(1);
gl.useProgram(programInfo.program);
twgl.setUniforms(programInfo, {time: now * 0.001});
ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 6, count);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
canvas { display: block; border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>
<div>fps: <span id="fps"></span></div>
<div>count: <input type="number" id="count" min="0" max="1000000" value="25000"></div>
Я не получаю ощутимой разницы. Попытка вашей функции круга
function main() {
const gl = document.querySelector('canvas').getContext('webgl');
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext) {
return alert('need ANGLE_instanced_arrays');
}
twgl.addExtensionsToContext(gl);
const vs = `
attribute float id;
attribute vec4 position;
attribute vec2 texcoord;
uniform float time;
varying vec2 v_texcoord;
varying vec4 v_color;
void main() {
float o = id + time;
gl_Position = position + vec4(
vec2(
fract(o * 0.1373),
fract(o * 0.5127)) * 2.0 - 1.0,
0, 0);
v_texcoord = texcoord;
v_color = vec4(fract(vec3(id) * vec3(0.127, 0.373, 0.513)), 1);
}`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
varying vec4 v_color;
float circle(in vec2 st, in float radius) {
vec2 dist = st - vec2(0.5);
return 1.0 - smoothstep(
radius - (radius * 0.01),
radius +(radius * 0.01),
dot(dist, dist) * 4.0);
}
void main() {
gl_FragColor = mix(
vec4(0),
v_color,
circle(v_texcoord, 1.0));
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const maxCount = 250000;
const ids = new Float32Array(maxCount);
for (let i = 0; i < ids.length; ++i) {
ids[i] = i;
}
const x = 16 / 300 * 2;
const y = 16 / 150 * 2;
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: {
numComponents: 2,
data: [
-x, -y,
x, -y,
-x, y,
-x, y,
x, -y,
x, y,
],
},
texcoord: [
0, 1,
1, 1,
0, 0,
0, 0,
1, 1,
1, 0,
],
id: {
numComponents: 1,
data: ids,
divisor: 1,
}
});
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
const fpsElem = document.querySelector('#fps');
const countElem = document.querySelector('#count');
let count;
function getCount() {
count = Math.min(maxCount, parseInt(countElem.value));
}
countElem.addEventListener('input', getCount);
getCount();
const maxHistory = 60;
const fpsHistory = new Array(maxHistory).fill(0);
let historyNdx = 0;
let historyTotal = 0;
let then = 0;
function render(now) {
const deltaTime = now - then;
then = now;
historyTotal += deltaTime - fpsHistory[historyNdx];
fpsHistory[historyNdx] = deltaTime;
historyNdx = (historyNdx + 1) % maxHistory;
fpsElem.textContent = (1000 / (historyTotal / maxHistory)).toFixed(1);
gl.useProgram(programInfo.program);
twgl.setUniforms(programInfo, {time: now * 0.001});
ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 6, count);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
canvas { display: block; border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>
<div>fps: <span id="fps"></span></div>
<div>count: <input type="number" id="count" min="0" max="1000000" value="25000"></div>
Я снова не получаю ощутимой разницы. Примечание: как я сказал выше, я получаю крайне противоречивые результаты в WebGL. Когда я запустил первый тест, я получил 28k при 60fps. Когда я побежал второй, я получил 23k. Я был удивлен, так как ожидал, что 2-й будет быстрее, поэтому я снова запустил первый и получил только 23 КБ. Последний раз я получил 29 тысяч, и снова был неожиданностью, но потом я вернулся и сделал предыдущий и получил 29 тысяч. По сути, это означает, что тестирование времени в WebGL практически невозможно. Движущихся частей так много, поскольку все они многопроцессные, поэтому получение постоянных результатов кажется невозможным.
Можно попробовать сбросить
function main() {
const gl = document.querySelector('canvas').getContext('webgl');
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext) {
return alert('need ANGLE_instanced_arrays');
}
twgl.addExtensionsToContext(gl);
const vs = `
attribute float id;
attribute vec4 position;
attribute vec2 texcoord;
uniform float time;
varying vec2 v_texcoord;
varying vec4 v_color;
void main() {
float o = id + time;
gl_Position = position + vec4(
vec2(
fract(o * 0.1373),
fract(o * 0.5127)) * 2.0 - 1.0,
0, 0);
v_texcoord = texcoord;
v_color = vec4(fract(vec3(id) * vec3(0.127, 0.373, 0.513)), 1);
}`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
varying vec4 v_color;
float circle(in vec2 st, in float radius) {
vec2 dist = st - vec2(0.5);
return 1.0 - smoothstep(
radius - (radius * 0.01),
radius +(radius * 0.01),
dot(dist, dist) * 4.0);
}
void main() {
if (circle(v_texcoord, 1.0) < 0.5) {
discard;
}
gl_FragColor = v_color;
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const maxCount = 250000;
const ids = new Float32Array(maxCount);
for (let i = 0; i < ids.length; ++i) {
ids[i] = i;
}
const x = 16 / 300 * 2;
const y = 16 / 150 * 2;
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: {
numComponents: 2,
data: [
-x, -y,
x, -y,
-x, y,
-x, y,
x, -y,
x, y,
],
},
texcoord: [
0, 1,
1, 1,
0, 0,
0, 0,
1, 1,
1, 0,
],
id: {
numComponents: 1,
data: ids,
divisor: 1,
}
});
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
const fpsElem = document.querySelector('#fps');
const countElem = document.querySelector('#count');
let count;
function getCount() {
count = Math.min(maxCount, parseInt(countElem.value));
}
countElem.addEventListener('input', getCount);
getCount();
const maxHistory = 60;
const fpsHistory = new Array(maxHistory).fill(0);
let historyNdx = 0;
let historyTotal = 0;
let then = 0;
function render(now) {
const deltaTime = now - then;
then = now;
historyTotal += deltaTime - fpsHistory[historyNdx];
fpsHistory[historyNdx] = deltaTime;
historyNdx = (historyNdx + 1) % maxHistory;
fpsElem.textContent = (1000 / (historyTotal / maxHistory)).toFixed(1);
gl.useProgram(programInfo.program);
twgl.setUniforms(programInfo, {time: now * 0.001});
ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 6, count);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
canvas { display: block; border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>
<div>fps: <span id="fps"></span></div>
<div>count: <input type="number" id="count" min="0" max="1000000" value="25000"></div>
Из-за непоследовательного выбора времени я не могу быть уверен, но мое впечатление, что сбрасывается медленнее. Сброс IIRC происходит медленно, потому что без сброса GPU знает еще до того, как он выполняет фрагментный шейдер, что он собирается обновить z-буфер, где, как и при сбросе, он не знает до тех пор, пока не выполнится шейдер, и что это различие означает, что определенные вещи не могутОптимизация тоже.
Я остановлюсь на этом, потому что есть слишком много комбинаций вещей, чтобы попробовать.
Мы могли бы попробовать смешаться. Смешивание также обычно медленнее, поскольку оно должно смешиваться (читать фон), но медленнее ли оно, чем отбрасывать? Я не знаю.
У вас есть тест глубины? Если это так, то порядок рисования будет важен.
Еще одна вещь, которую нужно проверить, - это использование не-четырехугольников, таких как шестиугольники или октогоны, поскольку это будет пропускать меньше пикселей через фрагментный шейдер. Я подозреваю, что вам, возможно, понадобится увеличить круги, чтобы увидеть это, но если у нас квадра 100x100 пикселей, то это 10k пикселей. Если у нас идеальная геометрия окружности, это примерно на pi * r ^ 2 или ~ 7853 или на 21% меньше пикселей. Шестиугольник будет ~ 8740 пикселей или на 11% меньше. Октогон где-то посередине. Рисование на 11–21% меньшего количества пикселей обычно является выигрышем, но, конечно, для шестиугольника вы будете рисовать в 3 раза больше треугольников, а для октогона - в 4 раза больше. Вам в основном нужно проверить все эти случаи.
Это указывает на еще одну проблему в том, что я полагаю, что вы получите другие относительные результаты с большими кругами на большем холсте, поскольку будет больше пикселей на круг, поэтому для любого заданного числа нарисованных кругов будет потрачено больше% времени. рисование пикселей и меньше вычисление вершин и / или меньшее время перезапуска графического процессора для рисования следующего круга.
Обновление
Тестирование в Chrome против Firefox Я получил 60k-66k во всех случаях в Chrome ната же машина. Не знаю, почему разница настолько велика, учитывая, что сам WebGL практически ничего не делает. Все 4 теста имеют только один вызов отрисовки на кадр. Но как бы то ни было, по крайней мере с 2019-10 Chrome более чем в два раза быстрее для этого конкретного случая, чем Firefox
Одна идея - у меня ноутбук с двумя GPU. Когда вы создаете контекст, вы можете сообщить WebGL, на что вы нацеливаетесь, передав атрибут создания контекста powerPreference
, как в
const gl = document.createContext('webgl', {
powerPreference: 'high-performance',
});
. Опции: «default», «low-power», «высокая производительность'. «по умолчанию» означает «пусть решит браузер», но в конечном итоге все они означают «пусть решит браузер». В любом случае, установка выше не изменила меня в Firefox.