Stage3D 2D Particle Test
1つのvertexバッファでの四角パーティクルの限界数(16383)を何バッファぶん描けるかテスト
16バッファぶん=16383*16≒512*512パーティクルまで
パーティクルの動きはただの壁反射
バッファ自体は共有。
pos...CPUでの座標計算
draw...バッファのアップロード+drawTriangles()+present()
ただし合計値がFPSと合わないのでdrawが正しく計れてない可能性あり
やっぱりCPUでの座標計算がネックか
/**
* Copyright 9balls ( http://wonderfl.net/user/9balls )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/eTBH
*/
package {
import com.adobe.utils.AGALMiniAssembler;
import com.bit101.components.NumericStepper;
import flash.display.Sprite;
import flash.display.Stage3D;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.display3D.Context3D;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DRenderMode;
import flash.display3D.Context3DTriangleFace;
import flash.display3D.Context3DVertexBufferFormat;
import flash.display3D.IndexBuffer3D;
import flash.display3D.Program3D;
import flash.display3D.VertexBuffer3D;
import flash.events.Event;
import flash.geom.Matrix3D;
import flash.text.TextField;
import flash.utils.ByteArray;
import flash.utils.getTimer;
import frocessing.color.ColorHSV;
/**
* ...
* @author
*/
public class Main extends Sprite {
private const NUM_PARTICLES:uint = 16383;
private const NUM_BUFFERS_MAX:uint = 16;
private const WIDTH:uint = 466;
private const HEIGHT:uint = 466;
private const SIZE:Number = 0.5;
private const RAD:Number = Math.PI / 180;
//
private var stage3D:Stage3D;
private var context3D:Context3D;
//
private var cpuPos:Vector.<Vector.<Number>>;
private var cpuVel:Vector.<Vector.<Number>>;
//Particle
private var programP:Program3D;
private var vBufferXY:VertexBuffer3D;
private var vBufferRGB:VertexBuffer3D;
private var vBufferOffset:VertexBuffer3D;
private var iBuffer:IndexBuffer3D;
//
private var numBuffers:uint = 1;
private var start:uint;
private var tfR:TextField;
private var tfP:TextField;
public function Main():void {
Wonderfl.disable_capture();
stage.frameRate = 60;
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
//set ui
addChild(new GPUStats());
var tfB:TextField = new TextField();
tfB.textColor = 0xffffff;
tfB.text = "Buffers : ";
tfB.x = 100;
addChild(tfB);
var stepper:NumericStepper = new NumericStepper(this, 150, 0, onStepper);
stepper.minimum = 1;
stepper.maximum = 16;
stepper.width = 50;
tfP = new TextField();
tfP.textColor = 0xffffff;
tfP.text = "Particle : " + NUM_PARTICLES;
tfP.x = 250;
addChild(tfP);
tfR = new TextField();
tfR.textColor = 0xffffff;
tfR.x = 400;
addChild(tfR);
//create input data
var count:uint = 0;
cpuPos = new Vector.<Vector.<Number>>(NUM_BUFFERS_MAX);
cpuVel = new Vector.<Vector.<Number>>(NUM_BUFFERS_MAX);
for (var j:int = 0; j < NUM_BUFFERS_MAX; j++){
cpuPos[j] = new Vector.<Number>(NUM_PARTICLES * 8);
cpuVel[j] = new Vector.<Number>(NUM_PARTICLES * 2);
var index:uint = 0;
for (var i:int = 0; i < NUM_PARTICLES; i++){
var posX:Number = WIDTH * Math.random();
var posY:Number = HEIGHT * Math.random();
cpuPos[j][index++] = posX;
cpuPos[j][index++] = posY;
cpuPos[j][index++] = posX;
cpuPos[j][index++] = posY;
cpuPos[j][index++] = posX;
cpuPos[j][index++] = posY;
cpuPos[j][index++] = posX;
cpuPos[j][index++] = posY;
}
index = 0;
for (i = 0; i < NUM_PARTICLES; i++){
var theta:Number = 2 * Math.PI * Math.random();
cpuVel[j][index++] = 2 * Math.cos(theta);
cpuVel[j][index++] = 2 * Math.sin(theta);
}
}
//
stage3D = stage.stage3Ds[0];
stage3D.x = 0;
stage3D.y = 0;
stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreate);
stage3D.requestContext3D(Context3DRenderMode.AUTO);
}
private function onContextCreate(e:Event):void {
context3D = stage3D.context3D;
//context3D.enableErrorChecking = true;
//
createShaders();
setConstant();
setBuffer();
//
context3D.configureBackBuffer(WIDTH, HEIGHT, 0, false);
context3D.setCulling(Context3DTriangleFace.BACK);
context3D.setVertexBufferAt(0, vBufferXY, 0, Context3DVertexBufferFormat.FLOAT_2);
context3D.setVertexBufferAt(1, vBufferRGB, 0, Context3DVertexBufferFormat.FLOAT_3);
context3D.setVertexBufferAt(2, vBufferOffset, 0, Context3DVertexBufferFormat.FLOAT_2);
context3D.setProgram(programP);
//
stage.addEventListener(Event.ENTER_FRAME, onExe);
}
private function onStepper(e:Event):void {
numBuffers = (e.currentTarget as NumericStepper).value;
tfP.text = "Particle : " + NUM_PARTICLES * numBuffers;
}
private function onExe(e:Event):void {
start = getTimer();
calcCPU();
var str:String = "pos : " + (getTimer() - start) + " ms";
//
start = getTimer();
context3D.clear(0, 0, 0, 1);
for (var i:int = 0; i < numBuffers; i++){
vBufferXY.uploadFromVector(cpuPos[i], 0, NUM_PARTICLES * 4);
context3D.drawTriangles(iBuffer);
GPUStats.setTRI(NUM_PARTICLES * 2);
}
context3D.present();
str += "\ndraw : " + (getTimer() - start) + " ms";
tfR.text = str;
}
private function calcCPU():void {
//calculate each position
var tmp:Number;
var pos:Vector.<Number>;
var vel:Vector.<Number>;
for (var j:int = 0; j < numBuffers; j++){
var index:uint = 0;
var index2:uint = 0;
pos = cpuPos[j];
vel = cpuVel[j];
for (var i:int = 0; i < NUM_PARTICLES; i++){
//x
tmp = pos[index] + vel[index2];
if (tmp >= WIDTH){
pos[index] = WIDTH;
pos[index + 2] = WIDTH;
pos[index + 4] = WIDTH;
pos[index + 6] = WIDTH;
vel[index2] *= -1;
} else if (tmp <= 0){
pos[index] = 0;
pos[index + 2] = 0;
pos[index + 4] = 0;
pos[index + 6] = 0;
vel[index2] *= -1;
} else {
pos[index] = tmp;
pos[index + 2] = tmp;
pos[index + 4] = tmp;
pos[index + 6] = tmp;
}
//y
tmp = pos[index + 1] + vel[index2 + 1];
if (tmp >= HEIGHT){
pos[index + 1] = HEIGHT;
pos[index + 3] = HEIGHT;
pos[index + 5] = HEIGHT;
pos[index + 7] = HEIGHT;
vel[index2 + 1] *= -1;
} else if (tmp <= 0){
pos[index + 1] = 0;
pos[index + 3] = 0;
pos[index + 5] = 0;
pos[index + 7] = 0;
vel[index2 + 1] *= -1;
} else {
pos[index + 1] = tmp;
pos[index + 3] = tmp;
pos[index + 5] = tmp;
pos[index + 7] = tmp;
}
index += 8;
index2 += 2;
}
}
}
private function createShaders():void {
var agalAssembler:AGALMiniAssembler = new AGALMiniAssembler();
//Particle
var code:String = "";
code += "add vt0, va0, va2\n";
code += "m44 vt0, vt0, vc0\n";
code += "mov op, vt0\n";
code += "mov v0, va1\n";
var vertexShader:ByteArray = agalAssembler.assemble(Context3DProgramType.VERTEX, code);
var fragmentShader:ByteArray = agalAssembler.assemble(Context3DProgramType.FRAGMENT, "mov oc, v0\n");
programP = context3D.createProgram();
programP.upload(vertexShader, fragmentShader);
GPUStats.vram += (vertexShader.length + fragmentShader.length) * 4;
}
private function setConstant():void {
var mtxP:Matrix3D = new Matrix3D();
mtxP.appendScale(4 / WIDTH, -4 / HEIGHT, 1);
mtxP.appendTranslation(-1, 1, 0);
context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, mtxP, true);
}
private function setBuffer():void {
//Particle
var numVertices:uint = NUM_PARTICLES * 4;
var numIndices:uint = NUM_PARTICLES * 6;
vBufferXY = context3D.createVertexBuffer(numVertices, 2);
GPUStats.vram += numVertices * 2 * 4;
//
vBufferRGB = context3D.createVertexBuffer(numVertices, 3);
GPUStats.vram += numVertices * 3 * 4;
var cVec:Vector.<Number> = new Vector.<Number>(numVertices * 3);
var color:ColorHSV = new ColorHSV();
var r:Number;
var g:Number;
var b:Number;
var index:uint = 0;
for (var i:int = 0; i < NUM_PARTICLES; i++){
color.value = 0x1000000 * Math.random();
r = color.r / 255.0;
g = color.g / 255.0;
b = color.b / 255.0;
cVec[index++] = r;
cVec[index++] = g;
cVec[index++] = b;
cVec[index++] = r;
cVec[index++] = g;
cVec[index++] = b;
cVec[index++] = r;
cVec[index++] = g;
cVec[index++] = b;
cVec[index++] = r;
cVec[index++] = g;
cVec[index++] = b;
}
vBufferRGB.uploadFromVector(cVec, 0, numVertices);
//
vBufferOffset = context3D.createVertexBuffer(numVertices, 2);
GPUStats.vram += numVertices * 2 * 4;
var offsetVec:Vector.<Number> = new Vector.<Number>(numVertices * 2);
index = 0;
for (i = 0; i < NUM_PARTICLES; i++){
offsetVec[index++] = -SIZE;
offsetVec[index++] = -SIZE;
offsetVec[index++] = -SIZE;
offsetVec[index++] = SIZE;
offsetVec[index++] = SIZE;
offsetVec[index++] = -SIZE;
offsetVec[index++] = SIZE;
offsetVec[index++] = SIZE;
}
vBufferOffset.uploadFromVector(offsetVec, 0, numVertices);
//
iBuffer = context3D.createIndexBuffer(numIndices);
GPUStats.vram += numIndices * 4;
var iVec:Vector.<uint> = new Vector.<uint>(numIndices);
index = 0;
var p:uint;
for (i = 0; i < NUM_PARTICLES; i++){
p = i << 2;
iVec[index++] = p;
iVec[index++] = p + 2;
iVec[index++] = p + 1;
iVec[index++] = p + 1;
iVec[index++] = p + 2;
iVec[index++] = p + 3;
}
iBuffer.uploadFromVector(iVec, 0, numIndices);
}
}
}
//////////////////////////////////////////////////
// GPUStats
//////////////////////////////////////////////////
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.system.System;
import flash.text.StyleSheet;
import flash.text.TextField;
import flash.utils.getTimer;
class GPUStats extends Sprite {
protected const WIDTH:uint = 70;
protected const HEIGHT:uint = 80;
//
protected var xml:XML;
protected var text:TextField;
protected var style:StyleSheet;
//
protected var timer:uint;
protected var fps:uint;
protected var ms:uint;
protected var ms_prev:uint;
protected var mem:Number;
protected var mem_max:Number = 0;
//
public static var vram:uint = 0;
public static var drw:uint = 0;
public static var tri:uint = 0;
//
protected var theme:Object = {bg: 0x000033, fps: 0xffff00, ms: 0x00ff00, mem: 0x00ffff, memmax: 0xff0070, vram: 0xffffff, drw: 0xffffff, tri: 0xffffff};
public function GPUStats(theme:Object = null):void {
if (!theme)
theme = this.theme;
//
xml = <xml><fps></fps><ms></ms><mem></mem><memMax></memMax><vram></vram><drw></drw><tri></tri></xml>;
style = new StyleSheet();
style.setStyle('xml', {fontSize: '9px', fontFamily: '_sans', leading: '-2px'});
for (var name:String in theme){
style.setStyle(name, {color: "#" + theme[name].toString(16)});
}
//
text = new TextField();
text.width = WIDTH;
text.height = HEIGHT;
text.styleSheet = style;
text.condenseWhite = true;
text.selectable = false;
text.mouseEnabled = false;
//
addEventListener(Event.ADDED_TO_STAGE, init, false, 0, true);
addEventListener(Event.REMOVED_FROM_STAGE, destroy, false, 0, true);
}
protected function init(e:Event):void {
graphics.beginFill(theme.bg);
graphics.drawRect(0, 0, WIDTH, HEIGHT);
graphics.endFill();
addChild(text);
addEventListener(MouseEvent.CLICK, onClick);
addEventListener(Event.ENTER_FRAME, update);
}
protected function destroy(e:Event):void {
graphics.clear();
removeChildren();
removeEventListener(MouseEvent.CLICK, onClick);
removeEventListener(Event.ENTER_FRAME, update);
}
protected function update(e:Event):void {
timer = getTimer();
if (timer - 1000 > ms_prev){
ms_prev = timer;
mem = Number((System.totalMemory * 0.000000954).toFixed(3));
mem_max = mem_max > mem ? mem_max : mem;
//
xml.fps = "FPS: " + fps + " / " + stage.frameRate;
xml.mem = "MEM: " + mem;
xml.memMax = "MAX: " + mem_max;
xml.vram = "VRAM: " + (vram * 0.000000954).toFixed(3)
xml.drw = "DRW: " + drw;
xml.tri = "TRI: " + tri;
//
fps = 0;
}
fps++;
xml.ms = "MS: " + (timer - ms);
ms = timer;
text.htmlText = xml;
drw = 0;
tri = 0;
}
protected function onClick(e:MouseEvent):void {
mouseY / height > .5 ? stage.frameRate-- : stage.frameRate++;
xml.fps = "FPS: " + fps + " / " + stage.frameRate;
text.htmlText = xml;
}
public static function setTRI(numTriangles:uint, drwCount:uint = 1):void {
tri += numTriangles;
drw += drwCount;
}
}