prosperon/scripts/render.js

1246 lines
32 KiB
JavaScript
Raw Normal View History

render.doc = {
doc: "Functions for rendering modes.",
normal: "Final render with all lighting.",
wireframe: "Show only wireframes of models."
};
2024-07-11 14:25:45 -05:00
var cur = {};
render.use_shader = function use_shader(shader)
2024-07-11 14:25:45 -05:00
{
2024-07-22 19:07:02 -05:00
if (typeof shader === 'string')
shader = make_shader(shader);
2024-07-11 14:25:45 -05:00
if (cur.shader === shader) return;
cur.shader = shader;
cur.bind = undefined;
cur.mesh = undefined;
render.setpipeline(shader.pipe);
shader_globals(cur.shader);
2024-07-11 14:25:45 -05:00
}
render.use_mat = function use_mat(mat)
2024-07-11 14:25:45 -05:00
{
if (!cur.shader) return;
if (cur.mat === mat) return;
shader_apply_material(cur.shader, mat, cur.mat);
2024-07-11 14:25:45 -05:00
cur.mat = mat;
cur.images = [];
if (!cur.shader.fs.images) return;
for (var img of cur.shader.fs.images)
if (mat[img.name])
cur.images.push(mat[img.name]);
else
cur.images.push(game.texture("icons/no_tex.gif"));
}
var models_array = [];
function set_model(t)
2024-07-11 14:25:45 -05:00
{
if (cur.shader.vs.unimap.model)
render.setunim4(0, cur.shader.vs.unimap.model.slot, t);
}
2024-08-08 17:32:58 -05:00
render.set_model = set_model;
var shaderlang = {
macos: "metal_macos",
2024-05-29 20:21:19 -05:00
windows: "hlsl5",
2024-05-16 14:50:18 -05:00
linux: "glsl430",
2024-05-21 18:50:53 -05:00
web: "wgsl",
ios: "metal_ios",
}
var attr_map = {
a_pos: 0,
a_uv: 1,
a_norm: 2,
2024-08-09 14:39:31 -05:00
a_joint: 3,
a_weight: 4,
a_color: 5,
a_tan: 6,
a_angle: 7,
a_wh: 8,
a_st: 9,
a_ppos: 10,
a_scale: 11
}
var blend_map = {
mix: true,
none: false
}
var primitive_map = {
point: 1,
line: 2,
linestrip: 3,
triangle: 4,
trianglestrip: 5
}
var cull_map = {
none: 1,
front: 2,
back: 3
}
var depth_map = {
off: false,
on: true
}
var face_map = {
cw: 2,
ccw: 1
}
render.poly_prim = function poly_prim(verts)
{
var index = [];
if (verts.length < 1) return undefined;
for (var i = 0; i < verts.length; i++)
verts[i][2] = 0;
for (var i = 2; i < verts.length; i++) {
index.push(0);
index.push(i-1);
index.push(i);
}
return {
pos: os.make_buffer(verts.flat()),
verts: verts.length,
index: os.make_buffer(index, 1),
count: index.length
};
}
function shader_directive(shader, name, map)
{
var reg = new RegExp(`#${name}.*`, 'g');
var mat = shader.match(reg);
2024-05-16 08:21:13 -05:00
if (!mat) return undefined;
reg = new RegExp(`#${name}\s*`, 'g');
var ff = mat.map(d=>d.replace(reg,''))[0].trim();
2024-05-16 08:21:13 -05:00
if (map) return map[ff];
return ff;
}
var uni_globals = {
time(stage, slot) { render.setuniv(stage, slot, profile.secs(profile.now())); },
projection(stage,slot) { render.setuniproj(stage, slot); },
view(stage,slot) { render.setuniview(stage, slot); },
vp(stage,slot) { render.setunivp(stage,slot); },
}
function set_global_uni(uni, stage) {
uni_globals[uni.name]?.(stage, uni.slot);
}
2024-07-14 16:09:50 -05:00
var setcam = render.set_camera;
render.set_camera = function(cam)
{
2024-07-23 12:02:46 -05:00
if (nextflush) {
nextflush();
nextflush = undefined;
}
2024-07-14 16:09:50 -05:00
delete cur.shader;
setcam(cam);
}
2024-07-22 15:40:58 -05:00
var shader_cache = {};
var shader_times = {};
function strip_shader_inputs(shader)
{
for (var a of shader.vs.inputs)
a.name = a.name.slice(2);
}
render.hotreload = function()
{
for (var i in shader_times) {
if (io.mod(i) <= shader_times[i]) continue;
say(`HOT RELOADING SHADER ${i}`);
shader_times[i] = io.mod(i);
var obj = create_shader_obj(i);
obj = obj[os.sys()];
2024-05-16 14:50:18 -05:00
obj.pipe = render.pipeline(obj);
var old = shader_cache[i];
Object.assign(shader_cache[i], obj);
cur.bind = undefined;
cur.mesh = undefined;
}
}
function create_shader_obj(file)
{
var files = [file];
var out = ".prosperon/tmp.shader";
var shader = io.slurp(file);
2024-05-30 17:12:32 -05:00
var incs = shader.match(/#include <.*>/g);
if (incs)
for (var inc of incs) {
var filez = inc.match(/#include <(.*)>/)[1];
var macro = io.slurp(filez);
2024-06-07 00:43:15 -05:00
if (!macro) {
filez = `shaders/${filez}`;
macro = io.slurp(filez);
}
shader = shader.replace(inc, macro);
files.push(filez);
}
var blend = shader_directive(shader, 'blend', blend_map);
var primitive = shader_directive(shader, 'primitive', primitive_map);
var cull = shader_directive(shader, 'cull', cull_map);
var depth = shader_directive(shader, 'depth', depth_map);
var face = shader_directive(shader, 'face', face_map);
2024-05-16 08:21:13 -05:00
var indexed = shader_directive(shader, 'indexed');
if (typeof indexed == 'undefined') indexed = true;
if (indexed === 'false') indexed = false;
shader = shader.replace(/uniform\s+(\w+)\s+(\w+);/g, "uniform _$2 { $1 $2; };");
shader = shader.replace(/(texture2D|sampler) /g, "uniform $1 ");
io.slurpwrite(out, shader);
2024-05-16 14:50:18 -05:00
var compiled = {};
// shader file is created, now cross compile to all targets
for (var platform in shaderlang) {
var backend = shaderlang[platform];
var ret = os.system(`sokol-shdc -f bare_yaml --slang=${backend} -i ${out} -o ${out}`);
if (ret) {
console.error(`error compiling shader ${file}. No compilation found for ${platform}:${backend}, and no cross compiler available.`);
return;
}
/* Take YAML and create the shader object */
var yamlfile = `${out}_reflection.yaml`;
2024-05-16 08:21:13 -05:00
var jjson = yaml.tojson(io.slurp(yamlfile));
2024-05-30 17:12:32 -05:00
var obj = json.decode(jjson);
io.rm(yamlfile);
obj = obj.shaders[0].programs[0];
function add_code(stage) {
stage.code = io.slurp(stage.path);
2024-05-16 14:50:18 -05:00
io.rm(stage.path);
delete stage.path;
}
2024-06-03 16:45:08 -05:00
add_code(obj.vs);
if (!obj.fs && obj.vs.fs) {
obj.fs = obj.vs.fs;
delete obj.vs.fs;
}
add_code(obj.fs);
obj.blend = blend;
obj.cull = cull;
obj.primitive = primitive;
obj.depth = depth;
obj.face = face;
2024-05-16 08:21:13 -05:00
obj.indexed = indexed;
if (obj.vs.inputs)
for (var i of obj.vs.inputs) {
if (!(i.name in attr_map))
i.mat = -1;
else
i.mat = attr_map[i.name];
}
function make_unimap(stage) {
if (!stage.uniform_blocks) return {};
var unimap = {};
for (var uni of stage.uniform_blocks) {
2024-05-16 14:50:18 -05:00
var uniname = uni.struct_name[0] == "_" ? uni.struct_name.slice(1) : uni.struct_name;
2024-05-16 14:50:18 -05:00
unimap[uniname] = {
name: uniname,
slot: Number(uni.slot),
size: Number(uni.size)
};
}
return unimap;
}
obj.vs.unimap = make_unimap(obj.vs);
obj.fs.unimap = make_unimap(obj.fs);
obj.name = file;
2024-05-16 14:50:18 -05:00
strip_shader_inputs(obj);
2024-05-16 14:50:18 -05:00
compiled[platform] = obj;
}
compiled.files = files;
compiled.source = shader;
return compiled;
}
function make_shader(shader)
{
if (shader_cache[shader]) return shader_cache[shader];
var file = shader;
shader = io.slurp(file);
if (!shader) {
console.info(`not found! slurping shaders/${file}`);
shader = io.slurp(`shaders/${file}`);
}
var writejson = `.prosperon/${file.name()}.shader.json`;
profile.cache("shader", file);
breakme: if (io.exists(writejson)) {
var data = json.decode(io.slurp(writejson));
var filemod = io.mod(writejson);
if (!data.files) break breakme;
for (var i of data.files) {
if (io.mod(i) > filemod) {
break breakme;
}
}
profile.endcache(" [cached]");
var shaderobj = json.decode(io.slurp(writejson));
var obj = shaderobj[os.sys()];
obj.pipe = render.pipeline(obj);
shader_cache[file] = obj;
shader_times[file] = io.mod(file);
return obj;
}
var compiled = create_shader_obj(file);
io.slurpwrite(writejson, json.encode(compiled));
2024-05-16 14:50:18 -05:00
var obj = compiled[os.sys()];
obj.pipe = render.pipeline(obj);
shader_cache[file] = obj;
shader_times[file] = io.mod(file);
return obj;
}
var shader_unisize = {
4: render.setuniv,
8: render.setuniv2,
12: render.setuniv3,
16: render.setuniv4
};
function shader_globals(shader)
{
for (var p in shader.vs.unimap)
set_global_uni(shader.vs.unimap[p], 0);
for (var p in shader.fs.unimap)
set_global_uni(shader.fs.unimap[p], 1);
}
function shader_apply_material(shader, material = {}, old = {})
{
for (var p in shader.vs.unimap) {
if (!(p in material)) continue;
2024-07-11 14:25:45 -05:00
if (material[p] === old[p]) continue;
assert(p in material, `shader ${shader.name} has no uniform for ${p}`);
var s = shader.vs.unimap[p];
2024-08-09 14:39:31 -05:00
if (p === 'bones') {
render.setunibones(0, s.slot, material[p]);
continue;
}
shader_unisize[s.size](0, s.slot, material[p]);
}
for (var p in shader.fs.unimap) {
if (!(p in material)) continue;
2024-07-11 14:25:45 -05:00
if (material[p] === old[p]) continue;
assert(p in material, `shader ${shader.name} has no uniform for ${p}`);
var s = shader.fs.unimap[p];
shader_unisize[s.size](1, s.slot, material[p]);
}
2024-07-11 14:25:45 -05:00
2024-06-19 18:52:41 -05:00
if (!material.diffuse) return;
2024-07-11 14:25:45 -05:00
if (material.diffuse === old.diffuse) return;
if ("diffuse_size" in shader.fs.unimap)
render.setuniv2(1, shader.fs.unimap.diffuse_size.slot, [material.diffuse.width, material.diffuse.height]);
2024-06-19 18:52:41 -05:00
if ("diffuse_size" in shader.vs.unimap)
2024-06-19 18:52:41 -05:00
render.setuniv2(0, shader.vs.unimap.diffuse_size.slot, [material.diffuse.width, material.diffuse.height]);
}
function sg_bind(mesh, ssbo)
{
2024-07-11 14:25:45 -05:00
cur.mesh = mesh;
var bind = {};
bind.attrib = [];
2024-07-11 14:25:45 -05:00
if (cur.shader.vs.inputs)
for (var a of cur.shader.vs.inputs) {
if (!(a.name in mesh)) {
console.error(`cannot draw shader ${cur.shader.name}; there is no attrib ${a.name} in the given mesh. ${json.encode(mesh)}`);
return undefined;
} else
bind.attrib.push(mesh[a.name]);
}
2024-07-11 14:25:45 -05:00
if (cur.shader.indexed) {
bind.index = mesh.index;
bind.count = mesh.count;
} else
bind.count = mesh.verts;
bind.ssbo = [];
2024-07-11 14:25:45 -05:00
if (cur.shader.vs.storage_buffers)
for (var b of cur.shader.vs.storage_buffers)
bind.ssbo.push(ssbo);
2024-07-11 14:25:45 -05:00
bind.inst = 1;
bind.images = cur.images;
cur.bind = bind;
render.setbind(cur.bind);
return bind;
}
2024-05-16 08:21:13 -05:00
render.device = {
pc: [1920,1080],
macbook_m2: [2560,1664, 13.6],
ds_top: [400,240, 3.53],
ds_bottom: [320,240, 3.02],
playdate: [400,240,2.7],
switch: [1280,720, 6.2],
switch_lite: [1280,720,5.5],
switch_oled: [1280,720,7],
dsi: [256,192,3.268],
ds: [256,192, 3],
dsixl: [256,192,4.2],
ipad_air_m2: [2360,1640, 11.97],
iphone_se: [1334, 750, 4.7],
iphone_12_pro: [2532,1170,6.06],
iphone_15: [2556,1179,6.1],
gba: [240,160,2.9],
gameboy: [160,144,2.48],
gbc: [160,144,2.28],
steamdeck: [1280,800,7],
vita: [960,544,5],
psp: [480,272,4.3],
imac_m3: [4480,2520,23.5],
macbook_pro_m3: [3024,1964, 14.2],
ps1: [320,240,5],
ps2: [640,480],
snes: [256,224],
gamecube: [640,480],
n64: [320,240],
c64: [320,200],
macintosh: [512,342,9],
gamegear: [160,144,3.2],
};
render.device.doc = `Device resolutions given as [x,y,inches diagonal].`;
2024-06-07 00:43:15 -05:00
var textshader;
var circleshader;
var polyshader;
2024-07-09 01:03:39 -05:00
var slice9shader;
2024-07-18 12:39:58 -05:00
var parshader;
2024-07-22 19:07:02 -05:00
var spritessboshader;
2024-07-23 12:02:46 -05:00
var polyssboshader;
2024-06-07 00:43:15 -05:00
2024-07-23 17:21:27 -05:00
var sprite_ssbo;
2024-06-07 00:43:15 -05:00
render.init = function() {
textshader = make_shader("shaders/text_base.cg");
render.spriteshader = make_shader("shaders/sprite.cg");
spritessboshader = make_shader("shaders/sprite_ssbo.cg");
render.postshader = make_shader("shaders/simplepost.cg");
slice9shader = make_shader("shaders/9slice.cg");
circleshader = make_shader("shaders/circle.cg");
polyshader = make_shader("shaders/poly.cg");
parshader = make_shader("shaders/baseparticle.cg");
polyssboshader = make_shader("shaders/poly_ssbo.cg");
2024-07-23 12:02:46 -05:00
poly_ssbo = render.make_textssbo();
2024-07-23 17:21:27 -05:00
sprite_ssbo = render.make_textssbo();
2024-06-07 00:43:15 -05:00
render.textshader = textshader;
2024-06-07 09:49:13 -05:00
os.make_circle2d().draw = function() {
render.circle(this.body().transform().pos, this.radius, [1,1,0,1]);
}
var disabled = [148/255,148/255, 148/255, 1];
var sleep = [1, 140/255, 228/255, 1];
var dynamic = [1, 70/255, 46/255, 1];
var kinematic = [1, 194/255, 64/255, 1];
var static_color = [73/255, 209/255, 80/255, 1];
os.make_poly2d().draw = function() {
var body = this.body();
var color = body.sleeping() ? [0,0.3,0,0.4] : [0,1,0,0.4];
var t = body.transform();
render.poly(this.points, color, body.transform());
color.a = 1;
render.line(this.points.wrapped(1), color, 1, body.transform());
}
os.make_seg2d().draw = function() {
render.line([this.a(), this.b()], [1,0,1,1], Math.max(this.radius/2, 1), this.body().transform());
}
joint.pin().draw = function() {
var a = this.bodyA();
var b = this.bodyB();
render.line([a.transform().pos.xy, b.transform().pos.xy], [0,1,1,1], 1);
}
2024-06-07 00:43:15 -05:00
}
2024-08-25 14:23:22 -05:00
render.draw_sprites = true;
render.draw_particles = true;
render.draw_hud = true;
render.draw_gui = true;
2024-08-26 11:13:26 -05:00
render.draw_gizmos = true;
2024-08-25 14:23:22 -05:00
render.sprites = function render_sprites(gridsize = 1)
2024-07-23 17:21:27 -05:00
{
2024-08-22 14:41:01 -05:00
// When y sorting, draw layer is firstly important followed by the gameobject position
2024-08-22 14:43:04 -05:00
var buckets;
if (render.y_sort) {
2024-08-22 14:41:01 -05:00
profile.frame("y bucketing");
var sps = Object.values(allsprites);
2024-08-22 14:43:04 -05:00
buckets = {};
2024-08-22 14:41:01 -05:00
for (var sprite of sps) {
var layer = (sprite.gameobject.drawlayer*10000)-sprite.gameobject.pos.y;
buckets[layer] ??= {};
if (buckets[layer][sprite.path])
buckets[layer][sprite.path].push(sprite);
else
buckets[layer][sprite.path] = [sprite];
}
profile.endframe();
profile.frame("y sorting");
buckets = Object.entries(buckets).sort((a,b) => {
var na = Number(a[0]);
var nb = Number(b[0]);
if (na < nb) return -1;
return 1;
});
profile.endframe();
2024-08-22 14:43:04 -05:00
} else {
profile.frame("sorting");
var sprite_buckets = component.sprite_buckets();
var buckets = Object.entries(sprite_buckets).sort((a,b) => {
var na = Number(a[0]);
var nb = Number(b[0]);
if (na < nb) return -1;
return 1;
});
profile.endframe();
}
/*
2024-07-25 10:33:51 -05:00
profile.frame("bucketing");
2024-07-23 17:21:27 -05:00
var sps = Object.values(allsprites);
var buckets = [];
for (var i = 0; i <= 20; i++)
buckets[i] = {};
2024-07-23 17:21:27 -05:00
for (var sprite of sps) {
var layer = sprite.gameobject.drawlayer+10;
if (buckets[layer][sprite.path])
buckets[layer][sprite.path].push(sprite);
else
buckets[layer][sprite.path] = [sprite];
2024-07-23 17:21:27 -05:00
}
2024-07-25 10:33:51 -05:00
profile.endframe();
2024-08-22 14:41:01 -05:00
*/
2024-07-25 10:33:51 -05:00
profile.frame("drawing");
2024-07-23 17:21:27 -05:00
render.use_shader(spritessboshader);
for (var layer of buckets) {
for (var img of Object.values(layer[1])) {
2024-07-23 17:21:27 -05:00
var sparray = Object.values(img);
if (sparray.length === 0) continue;
var ss = sparray[0];
render.use_mat(ss);
render.make_sprite_ssbo(sparray, sprite_ssbo);
2024-07-23 17:21:27 -05:00
render.draw(shape.quad, sprite_ssbo, sparray.length);
2024-08-15 12:26:37 -05:00
if (debug.sprite_nums) render.text(ss.diffuse.getid(), ss.transform.pos);
2024-07-23 17:21:27 -05:00
}
}
2024-07-25 10:33:51 -05:00
profile.endframe();
}
2024-07-23 17:21:27 -05:00
render.circle = function render_circle(pos, radius, color, inner_radius = 1) {
2024-09-14 09:36:40 -05:00
check_flush(undefined);
if (inner_radius >= 1)
inner_radius = inner_radius/radius;
else if (inner_radius < 0)
inner_radius = 1.0;
2024-06-07 00:43:15 -05:00
var mat = {
radius: radius,
inner_r: inner_radius,
2024-06-07 00:43:15 -05:00
coord: pos,
shade: color,
2024-06-07 00:43:15 -05:00
};
2024-07-11 14:25:45 -05:00
render.use_shader(circleshader);
render.use_mat(mat);
render.draw(shape.quad);
2024-06-07 00:43:15 -05:00
}
render.circle.doc = "Draw a circle at pos, with a given radius and color. If inner_radius is between 0 and 1, it acts as a percentage of radius. If it is above 1, is acts as a unit (usually a pixel).";
2024-06-07 00:43:15 -05:00
render.poly = function render_poly(points, color, transform) {
2024-06-07 00:43:15 -05:00
var buffer = render.poly_prim(points);
var mat = { shade: color};
2024-07-11 14:25:45 -05:00
render.use_shader(polyshader);
set_model(transform);
2024-07-11 14:25:45 -05:00
render.use_mat(mat);
render.draw(buffer);
2024-06-07 00:43:15 -05:00
}
2024-07-23 12:02:46 -05:00
var nextflush = undefined;
function flush()
{
nextflush?.();
nextflush = undefined;
}
2024-09-23 18:17:46 -05:00
// If flush_fn was already on deck, it does not flush. Otherwise, flushes and then sets the flush fn
function check_flush(flush_fn)
2024-07-23 12:02:46 -05:00
{
if (!nextflush)
nextflush = flush_fn;
else if (nextflush !== flush_fn) {
nextflush();
nextflush = flush_fn;
}
2024-07-23 12:02:46 -05:00
}
2024-09-13 10:27:01 -05:00
render.flush = check_flush;
2024-07-23 12:02:46 -05:00
var poly_cache = [];
var poly_idx = 0;
2024-07-23 12:02:46 -05:00
var poly_ssbo;
function poly_e()
{
var e;
poly_idx++;
if (poly_idx > poly_cache.length) {
e = {
transform:os.make_transform(),
color: Color.white
};
poly_cache.push(e);
return e;
}
var e = poly_cache[poly_idx-1];
e.transform.unit();
return e;
}
function flush_poly()
2024-07-23 12:02:46 -05:00
{
if (poly_idx === 0) return;
2024-07-23 12:02:46 -05:00
render.use_shader(polyssboshader);
render.use_mat({});
render.make_particle_ssbo(poly_cache.slice(0,poly_idx), poly_ssbo);
2024-08-24 18:40:29 -05:00
render.draw(shape.centered_quad, poly_ssbo, poly_idx);
poly_idx = 0;
2024-07-23 12:02:46 -05:00
}
2024-09-23 18:17:46 -05:00
function flush_image()
{
}
render.line = function render_line(points, color = Color.white, thickness = 1) {
2024-08-28 16:38:31 -05:00
for (var i = 0; i < points.length-1; i++) {
var a = points[i];
var b = points[i+1];
var poly = poly_e();
var dist = vector.distance(a,b);
poly.transform.move(vector.midpoint(a,b));
poly.transform.rotate([0,0,-1], vector.angle([b.x-a.x, b.y-a.y]));
poly.transform.scale = [dist, thickness, 1];
poly.color = color;
}
check_flush(flush_poly);
2024-06-07 00:43:15 -05:00
}
2024-05-16 08:21:13 -05:00
/* All draw in screen space */
render.point = function(pos,size,color = Color.blue) {
2024-05-30 12:05:51 -05:00
render.circle(pos,size,size,color);
2024-05-16 08:21:13 -05:00
};
render.cross = function render_cross(pos, size, color = Color.red, thickness = 1) {
2024-05-21 09:33:17 -05:00
var a = [
pos.add([0,size]),
pos.add([0,-size])
];
var b = [
pos.add([size,0]),
pos.add([-size,0])
];
2024-07-03 16:38:29 -05:00
render.line(a,color,thickness);
render.line(b,color,thickness);
2024-05-21 09:33:17 -05:00
};
2024-05-16 08:21:13 -05:00
render.arrow = function render_arrow(start, end, color = Color.red, wingspan = 4, wingangle = 10) {
2024-05-16 08:21:13 -05:00
var dir = end.sub(start).normalized();
var wing1 = [
Vector.rotate(dir, wingangle).scale(wingspan).add(end),
end
];
var wing2 = [
Vector.rotate(dir,-wingangle).scale(wingspan).add(end),
end
];
render.line([start,end],color);
render.line(wing1,color);
render.line(wing2,color);
};
render.coordinate = function render_coordinate(pos, size, color) {
2024-05-16 08:21:13 -05:00
render.text(JSON.stringify(pos.map(p=>Math.round(p))), pos, size, color);
render.point(pos, 2, color);
}
render.boundingbox = function render_boundingbox(bb, color = Color.white) {
2024-07-10 09:39:28 -05:00
render.line(bbox.topoints(bb).wrapped(1), color);
2024-05-16 08:21:13 -05:00
}
render.rectangle = function render_rectangle(lowerleft, upperright, color) {
2024-07-23 14:30:41 -05:00
var transform = os.make_transform();
var wh = [upperright.x-lowerleft.x, upperright.y-lowerleft.y];
var poly = poly_e();
poly.transform.move(vector.midpoint(lowerleft,upperright));
poly.transform.scale = [wh.x,wh.y,1];
poly.color = color;
check_flush(flush_poly);
2024-05-16 08:21:13 -05:00
};
render.box = function render_box(pos, wh, color = Color.white) {
var poly = poly_e();
poly.transform.move(pos);
poly.transform.scale = [wh.x,wh.y,1];
poly.color = color;
check_flush(flush_poly);
2024-05-16 08:21:13 -05:00
};
render.window = function render_window(pos, wh, color) { render.box(pos.add(wh.scale(0.5)),wh,color); };
2024-05-16 08:21:13 -05:00
2024-07-09 13:48:15 -05:00
render.text_bb = function(str, size = 1, wrap = -1, pos = [0,0])
{
var bb = render.text_size(str,size,wrap);
2024-07-09 01:03:39 -05:00
var w = (bb.r - bb.l);
var h = (bb.t - bb.b);
2024-07-09 13:48:15 -05:00
2024-07-09 01:03:39 -05:00
bb.r += pos.x;
bb.l += pos.x;
bb.t += pos.y;
bb.b += pos.y;
2024-07-09 13:48:15 -05:00
return bb;
}
render.text = function(str, pos, size = 1, color = Color.white, wrap = -1, anchor = [0,1], cursor = -1) {
2024-07-10 09:39:28 -05:00
var bb = render.text_bb(str, size, wrap, pos);
2024-09-06 22:47:04 -05:00
gui.text(str, pos, size, color, wrap, cursor); // this puts text into buffer
2024-07-23 12:02:46 -05:00
check_flush(render.flush_text);
2024-07-09 01:03:39 -05:00
return bb;
2024-05-16 08:21:13 -05:00
p.x -= w * anchor.x;
bb.r += (w*anchor.x);
bb.l += (w*anchor.x);
p.y += h * (1 - anchor.y);
bb.t += h*(1-anchor.y);
bb.b += h*(1-anchor.y);
2024-07-09 01:03:39 -05:00
2024-05-16 08:21:13 -05:00
return bb;
};
2024-09-23 18:17:46 -05:00
var lasttex = undefined;
var img_cache = [];
var img_idx = 0;
function flush_img()
{
if (img_idx === 0) return;
render.use_shader(spritessboshader);
render.use_mat({diffuse:lasttex});
render.make_sprite_ssbo(img_cache.slice(0,img_idx), poly_ssbo);
render.draw(shape.quad, poly_ssbo, img_idx);
lasttex = undefined;
img_idx = 0;
}
function img_e()
{
img_idx++;
if (img_idx > img_cache.length) {
e = {
transform: os.make_transform(),
shade: Color.white,
rect: [0,0,1,1]
};
img_cache.push(e);
return e;
}
var e = img_cache[img_idx-1];
e.transform.unit();
return e;
}
render.image = function(tex, pos, scale, rotation = 0, color = Color.white) {
2024-09-13 14:34:54 -05:00
if (typeof tex === 'string') {
2024-09-11 12:25:42 -05:00
tex = game.texture(tex);
2024-09-13 14:34:54 -05:00
scale.x ??= tex.width;
scale.y ??= tex.height;
}
2024-09-11 12:25:42 -05:00
if (!tex) return;
2024-09-23 18:17:46 -05:00
if (!lasttex) {
check_flush(flush_img);
lasttex = tex;
}
if (lasttex !== tex) {
flush_img();
lasttex = tex;
}
2024-07-09 01:03:39 -05:00
2024-09-23 18:17:46 -05:00
var e = img_e();
e.transform.move(pos);
if (scale)
e.transform.scale = scale.div([tex.width, tex.height]);
e.shade = color;
return;
2024-07-09 01:03:39 -05:00
var bb = {};
bb.b = pos.y;
bb.l = pos.x;
bb.t = pos.y + tex.height*scale;
bb.r = pos.x + tex.width*scale;
return bb;
}
2024-07-15 15:54:18 -05:00
// pos is the lower left corner, scale is the width and height
2024-07-09 16:43:09 -05:00
render.slice9 = function(tex, pos, bb, scale = [tex.width,tex.height], color = Color.white)
2024-07-09 01:03:39 -05:00
{
var t = os.make_transform();
t.pos = pos;
2024-07-09 16:43:09 -05:00
t.scale = [scale.x/tex.width,scale.y/tex.height,1];
var border;
if (typeof bb === 'number')
border = [bb/tex.width,bb/tex.height,bb/tex.width,bb/tex.height];
else
border = [bb.l/tex.width, bb.b/tex.height, bb.r/tex.width, bb.t/tex.height];
2024-07-11 14:25:45 -05:00
render.use_shader(slice9shader);
set_model(t);
2024-07-11 14:25:45 -05:00
render.use_mat({
2024-07-09 13:48:15 -05:00
shade: color,
diffuse:tex,
rect:[0,0,1,1],
2024-07-09 16:43:09 -05:00
border: border,
scale: [scale.x/tex.width,scale.y/tex.height]
2024-07-09 01:03:39 -05:00
});
2024-07-11 14:25:45 -05:00
render.draw(shape.quad);
2024-07-09 01:03:39 -05:00
}
2024-09-06 22:47:04 -05:00
function endframe()
{
tdraw = 0;
}
var textssbos = [];
var tdraw = 0;
2024-07-18 17:09:35 -05:00
2024-07-09 01:03:39 -05:00
render.flush_text = function()
{
if (!render.textshader) return;
2024-09-06 22:47:04 -05:00
tdraw++;
if (textssbos.length < tdraw)
textssbos.push(render.make_textssbo());
2024-09-07 00:11:34 -05:00
var textssbo = textssbos[tdraw-1];
2024-09-06 22:47:04 -05:00
var amt = render.flushtext(textssbo); // load from buffer into ssbo
if (amt === 0) {
tdraw--;
return;
}
2024-07-18 17:09:35 -05:00
2024-07-11 14:25:45 -05:00
render.use_shader(render.textshader);
render.use_mat({text:render.font.texture});
2024-07-18 17:09:35 -05:00
render.draw(shape.quad, textssbo, amt);
2024-05-16 08:21:13 -05:00
}
var fontcache = {};
2024-05-16 08:21:13 -05:00
render.set_font = function(path, size) {
var fontstr = `${path}-${size}`;
if (render.font && fontcache[fontstr] === render.font) return;
if (!fontcache[fontstr]) fontcache[fontstr] = os.make_font(path, size);
2024-07-09 01:03:39 -05:00
render.flush_text();
2024-05-16 08:21:13 -05:00
gui.font_set(fontcache[fontstr]);
render.font = fontcache[fontstr];
2024-05-16 08:21:13 -05:00
}
render.doc = "Draw shapes in screen space.";
render.cross.doc = "Draw a cross centered at pos, with arm length size.";
render.arrow.doc = "Draw an arrow from start to end, with wings of length wingspan at angle wingangle.";
render.rectangle.doc = "Draw a rectangle, with its corners at lowerleft and upperright.";
2024-09-07 00:11:34 -05:00
render.draw = function render_draw(mesh, ssbo, inst = 1, e_start = 0)
2024-07-11 14:25:45 -05:00
{
sg_bind(mesh, ssbo);
profile.frame("gpu");
2024-09-07 00:11:34 -05:00
render.spdraw(e_start, cur.bind.count, inst);
profile.endframe();
2024-07-11 14:25:45 -05:00
}
2024-05-16 08:21:13 -05:00
// Returns an array in the form of [left, bottom, right, top] in pixels of the camera to render to
2024-08-22 13:31:00 -05:00
// Camera viewport is [left,bottom,width,height] in relative values
function camviewport()
{
var aspect = (this.viewport[2]-this.viewport[0])/(this.viewport[3]-this.viewport[1])*window.size.x/window.size.y;
var raspect = this.size.x/this.size.y;
var left = this.viewport[0]*window.size.x;
var bottom = this.viewport[1]*window.size.y;
var usemode = this.mode;
if (this.break && this.size.x > window.size.x && this.size.y > window.size.y)
usemode = this.break;
if (usemode === "fit")
if (raspect < aspect) usemode = "height";
else usemode = "width";
switch(usemode) {
case "stretch":
case "expand":
return [0, 0, window.size.x, window.size.y];
case "keep":
return [left, bottom, left+this.size.x, bottom+this.size.y];
case "height":
var ret = [left, 0, this.size.x*(window.size.y/this.size.y), window.size.y];
ret[0] = (window.size.x-(ret[2]-ret[0]))/2;
return ret;
case "width":
var ret = [0, bottom, window.size.x, this.size.y*(window.size.x/this.size.x)];
ret[1] = (window.size.y-(ret[3]-ret[1]))/2;
return ret;
}
return [0, 0, window.size.x, window.size.y];
}
// pos is pixels on the screen, lower left[0,0]
function camscreen2world(pos)
{
var view = this.screen2cam(pos);
view.x *= this.size.x;
view.y *= this.size.y;
view = view.sub([this.size.x/2, this.size.y/2]);
view = view.add(this.pos.xy);
return view;
}
2024-08-22 13:31:00 -05:00
// three coordinatse
// world coordinates, the "actual" view relative to the game's universe
// camera coordinates, normalized from 0 to 1 inside of a camera's viewport, bottom left is 0,0, top right is 1,1
// screen coordinates, pixels, 0,0 at the bottom left of the window and [w,h] at the top right of the screen
camscreen2world.doc = "Convert a view position for a camera to world."
2024-08-22 13:31:00 -05:00
// return camera coordinates given a screen position
function screen2cam(pos)
{
var viewport = this.view();
2024-08-22 13:31:00 -05:00
var width = viewport[2];
var height = viewport[3];
var viewpos = pos.sub([viewport[0],viewport[1]]);
return viewpos.div([width,height]);
}
function camextents()
{
var half = this.size;//.scale(0.5);
return {
l: this.pos.x-half.x,
r: this.pos.x+half.x,
t:this.pos.y+half.y,
b:this.pos.y-half.y
};
}
screen2cam.doc = "Convert a screen space position in pixels to a normalized viewport position in a camera."
2024-08-27 14:58:08 -05:00
prosperon.gizmos = function()
{
game.all_objects(o => {
if (o.gizmo) render.image(game.texture(o.gizmo), o.pos);
});
}
prosperon.make_camera = function()
{
var cam = world.spawn();
cam.near = 0.1;
cam.far = 1000;
cam.ortho = true;
cam.viewport = [0,0,1,1];
cam.size = window.size.slice(); // The render size of this camera in pixels
// In ortho mode, this determines how many pixels it will see
cam.mode = "stretch";
cam.screen2world = camscreen2world;
cam.screen2cam = screen2cam;
2024-08-22 13:31:00 -05:00
cam.extents = camextents;
cam.mousepos = function() { return this.screen2world(input.mouse.screenpos()); }
cam.view = camviewport;
cam.offscreen = false;
return cam;
}
var screencolor;
2024-09-03 14:08:46 -05:00
globalThis.imtoggle = function(name, obj, field) { obj[field] = imgui.checkbox(name, obj[field]); }
var replstr = "";
var imdebug = function()
{
imtoggle("Physics", debug, 'draw_phys');
imtoggle("Bouning boxes", debug, 'draw_bb');
imtoggle("Gizmos", debug, 'draw_gizmos');
imtoggle("Names", debug, 'draw_names');
imtoggle("Sprite nums", debug, 'sprite_nums');
imtoggle("Debug overlay", debug, 'show');
imtoggle("Show ur names", debug, 'urnames');
}
var imgui_fn = function()
{
render.imgui_new(window.size.x, window.size.y, 0.01);
if (debug.console) debug.console = imgui.window("console", _ => { imgui.text(console.transcript); replstr = imgui.textinput(undefined, replstr); imgui.button("submit", _ => { eval(replstr); replstr = ""; }); });
imgui.mainmenubar(_=>{
imgui.menu("File", _ => {
imgui.menu("Game settings", _ => {
window.title = imgui.textinput("Title", window.title);
window.icon = imgui.textinput("Icon", window.icon);
imgui.button("Refresh window", _ => {
window.set_icon(game.texture(window.icon));
});
});
imgui.button("quit", os.quit);
});
imgui.menu("Debug", imdebug);
imgui.menu("View", _ => {
2024-09-04 14:34:45 -05:00
imtoggle("Profiler", debug, 'showprofiler');
2024-09-03 14:08:46 -05:00
imtoggle("Terminal out", debug, 'termout');
imtoggle("Meta [f7]", debug, 'meta');
imtoggle("Cheats [f8]", debug, 'cheat');
imtoggle("Console [f9]", debug, 'console');
});
imgui.sokol_gfx();
imgui.menu("Graphics", _ => {
imtoggle("Draw sprites", render, 'draw_sprites');
imtoggle("Draw particles", render, 'draw_particles');
imtoggle("Draw HUD", render, 'draw_hud');
imtoggle("Draw GUI", render, 'draw_gui');
imtoggle("Draw gizmos", render, 'draw_gizmos');
imgui.menu("Window", _ => {
window.fullscreen = imgui.checkbox("fullscreen", window.fullscreen);
// window.vsync = imgui.checkbox("vsync", window.vsync);
imgui.menu("MSAA", _ => {
for (var msaa of gamestate.msaa)
imgui.button(msaa + "x", _ => window.sample_count = msaa);
});
imgui.menu("Resolution", _ => {
for (var res of gamestate.resolutions)
imgui.button(res, _ => window.resolution = res);
});
});
});
prosperon.menu_hook?.();
});
prosperon.imgui();
render.imgui_end();
}
prosperon.render = function()
{
profile.frame("world");
render.set_camera(prosperon.camera);
profile.frame("sprites");
2024-08-25 14:23:22 -05:00
if (render.draw_sprites) render.sprites();
if (render.draw_particles) draw_emitters();
profile.endframe();
profile.frame("draws");
prosperon.draw();
profile.endframe();
prosperon.hudcam.size = prosperon.camera.size;
prosperon.hudcam.transform.pos = [prosperon.hudcam.size.x/2, prosperon.hudcam.size.y/2, -100];
render.set_camera(prosperon.hudcam);
profile.endframe();
profile.frame("hud");
2024-08-25 14:23:22 -05:00
if (render.draw_hud) prosperon.hud();
2024-08-27 14:58:08 -05:00
render.flush_text();
render.set_camera(prosperon.camera);
2024-08-26 11:13:26 -05:00
if (render.draw_gizmos && prosperon.gizmos) prosperon.gizmos();
render.flush_text();
render.end_pass();
profile.endframe();
2024-08-05 15:26:18 -05:00
profile.endframe();
profile.endframe();
/* draw the image of the game world first */
render.glue_pass();
2024-08-05 15:26:18 -05:00
profile.frame("frame");
profile.frame("render");
profile.frame("post process");
render.viewport(...prosperon.camera.view());
render.use_shader(render.postshader);
render.use_mat({diffuse:prosperon.screencolor});
render.draw(shape.quad);
profile.endframe();
profile.frame("app");
// Flush & render
prosperon.appcam.transform.pos = [window.size.x/2, window.size.y/2, -100];
prosperon.appcam.size = window.size.slice();
if (os.sys() !== 'macos')
prosperon.appcam.size.y *= -1;
render.set_camera(prosperon.appcam);
render.viewport(...prosperon.appcam.view());
// Call gui functions
mum.style = mum.dbg_style;
2024-08-25 14:23:22 -05:00
if (render.draw_gui) prosperon.gui();
if (mum.drawinput) mum.drawinput();
render.flush_text();
mum.style = mum.base;
2024-09-09 18:55:07 -05:00
check_flush();
profile.endframe();
profile.frame("imgui");
2024-09-03 14:08:46 -05:00
if (debug.show)
imgui_fn();
profile.endframe();
render.end_pass();
render.commit();
2024-09-06 22:47:04 -05:00
endframe();
}
prosperon.process = function process() {
profile.frame("frame");
var dt = profile.secs(profile.now()) - frame_t;
frame_t = profile.secs(profile.now());
2024-09-24 17:07:32 -05:00
var sst = profile.now();
2024-08-06 14:23:21 -05:00
/* debugging: check for gc */
profile.print_gc();
var cycles = os.check_cycles();
if (cycles) say(cycles);
2024-09-24 17:07:32 -05:00
profile.frame("app update");
prosperon.appupdate(dt);
profile.endframe();
profile.frame("input");
input.procdown();
profile.endframe();
if (sim.mode === "play" || sim.mode === "step") {
profile.frame("update");
prosperon.update(dt * game.timescale);
2024-08-25 14:23:22 -05:00
update_emitters(dt * game.timescale);
profile.endframe();
if (sim.mode === "step") sim.pause();
2024-09-24 17:07:32 -05:00
}
profile.pushdata(profile.data.cpu.scripts, profile.now()-sst);
sst = profile.now();
2024-09-24 17:07:32 -05:00
if (sim.mode === "play" || sim.mode === "step") {
profile.frame("physics");
physlag += dt;
while (physlag > physics.delta) {
physlag -= physics.delta;
prosperon.phys2d_step(physics.delta * game.timescale);
prosperon.physupdate(physics.delta * game.timescale);
}
profile.endframe();
2024-09-24 17:07:32 -05:00
profile.pushdata(profile.data.cpu.physics, profile.now()-sst);
sst = profile.now();
}
profile.frame("render");
prosperon.window_render(window.size);
prosperon.render();
2024-09-24 17:07:32 -05:00
profile.pushdata(profile.data.cpu.render, profile.now()-sst);
profile.endframe();
profile.endframe();
2024-09-24 14:12:59 -05:00
profile.capture_data();
}
return {render};