Files
cell/prosperon/prosperon.cm
2025-07-29 15:15:18 -05:00

620 lines
15 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

var prosperon = {}
// This file is hard coded for the SDL GPU case
var video = use('sdl_video')
var surface = use('surface')
var sdl_gpu = use('sdl_gpu')
var io = use('io')
var geometry = use('geometry')
var os = use('os')
// suppose your rendersurface is W×H pixels, and
// your camera.viewport = { x:0, y:0, width:1, height:1 };
// so vpW = W*1, vpH = H*1
def vpW = 640
def vpH = 360
// how many worldunits from center to left/right?
// if zoom = 1 means 1 worldunit == 1 pixel:
def halfW = vpW * 0.5;
def halfH = vpH * 0.5;
// define your clippingvolume in cameraspace:
def l = -halfW, r = +halfW;
def b = -halfH, t = +halfH;
def n = 0, f = 1;
// Metal wants z in [0,1], so we use the “zerotoone” variant:
function makeOrthoMetal(l,r,b,t,n,f){
return [
2/(r-l), 0, 0, 0,
0, 2/(t-b), 0, 0,
0, 0, 1/(f-n), 0,
-(r+l)/(r-l), -(t+b)/(t-b), -n/(f-n), 1
]
}
def P = makeOrthoMetal(l,r,b,t,n,f);
var Pblob = geometry.array_blob(P)
log.console(Pblob.length)
var driver = "vulkan"
switch(os.platform()) {
case "Linux":
driver = "vulkan"
break
case "Windows":
// driver = "direct3d12"
driver = "vulkan"
break
case "macOS":
driver = "metal"
break
}
var default_sampler = {
min_filter: "nearest",
mag_filter: "nearest",
mipmap: "linear",
u: "repeat",
v: "repeat",
w: "repeat",
mip_bias: 0,
max_anisotropy: 0,
compare_op: "none",
min_lod: 0,
max_lod: 0,
anisotropy: false,
compare: false
};
var main_color = {
type:"2d",
format: "rgba8",
layers: 1,
mip_levels: 1,
samples: 0,
sampler:true,
color_target:true
};
var main_depth = {
type: "2d",
format: "d32 float s8",
layers:1,
mip_levels:1,
samples:0,
sampler:true,
depth_target:true
};
var default_window = {
// Basic properties
title: "Prosperon Window",
width: 640,
height: 360,
// Position - can be numbers or "centered"
x: null, // SDL_WINDOWPOS_null by default
y: null, // SDL_WINDOWPOS_null by default
// Window behavior flags
resizable: true,
fullscreen: false,
hidden: false,
borderless: false,
alwaysOnTop: false,
minimized: false,
maximized: false,
// Input grabbing
mouseGrabbed: false,
keyboardGrabbed: false,
// Display properties
highPixelDensity: false,
transparent: false,
opacity: 1.0, // 0.0 to 1.0
// Focus behavior
notFocusable: false,
// Special window types (mutually exclusive)
utility: false, // Utility window (not in taskbar)
tooltip: false, // Tooltip window (requires parent)
popupMenu: false, // Popup menu window (requires parent)
// Graphics API flags (let SDL choose if not specified)
opengl: false, // Force OpenGL context
vulkan: false, // Force Vulkan context
metal: false, // Force Metal context (macOS)
// Advanced properties
parent: null, // Parent window for tooltips/popups/modal
modal: false, // Modal to parent window (requires parent)
externalGraphicsContext: false, // Use external graphics context
// Input handling
textInput: true, // Enable text input on creation
}
var win_config = arg[0] || {}
win_config.__proto__ = default_window
win_config.metal = true
var window = new video.window(win_config)
var device = new sdl_gpu.gpu({
shaders_msl:true,
shaders_metallib:true,
name: "metal"
})
device.claim_window(window)
device.set_swapchain(window, 'sdr', 'vsync')
var shader_type = device.shader_format()[0]
shader_type = 'msl'
var std_sampler = new sdl_gpu.sampler(device, default_sampler)
// Shader and pipeline cache
var shader_cache = {}
var pipeline_cache = {}
function upload(copypass, buffer, toblob)
{
var trans = new sdl_gpu.transfer_buffer(device, {
size: toblob.length/8,
usage:"upload"
})
trans.copy_blob(device, toblob)
copypass.upload_to_buffer({
transfer_buffer: trans,
offset:0
}, {
buffer: buffer,
offset: 0,
size: toblob.length/8
})
}
function make_shader(sh_file)
{
var file = `shaders/${shader_type}/${sh_file}.${shader_type}`
if (shader_cache[file]) return shader_cache[file]
var refl = json.decode(io.slurp(`shaders/reflection/${sh_file}.json`))
var shader = {
code: io.slurpbytes(file),
format: shader_type,
stage: sh_file.endsWith("vert") ? "vertex" : "fragment",
num_samplers: refl.separate_samplers ? refl.separate_samplers.length : 0,
num_textures: 0,
num_storage_buffers: refl.separate_storage_buffers ? refl.separate_storage_buffers.length : 0,
num_uniform_buffers: refl.ubos ? refl.ubos.length : 0,
entrypoint: shader_type == "msl" ? "main0" : "main"
}
shader.gpu = new sdl_gpu.shader(device, shader)
shader.reflection = refl;
shader_cache[file] = shader
shader.file = sh_file
return shader
}
var usepipe
function load_pipeline(config)
{
log.console(usepipe)
if (usepipe) return usepipe
config.vertex = make_shader(config.vertex).gpu
config.fragment = make_shader(config.fragment).gpu
log.console(json.encode(config))
log.console("ANOTHER NEW PIPELINE")
log.console(config)
usepipe = new sdl_gpu.graphics_pipeline(device, config)
log.console(usepipe)
return usepipe
}
// Initialize ImGui with the window and renderer
//imgui.init(window, renderer)
//imgui.newframe()
var io = use('io');
var rasterize = use('rasterize');
var time = use('time')
var tilemap = use('tilemap')
var res = use('resources')
var input = use('input')
var graphics = use('graphics')
var camera = {}
prosperon.scissor = function(rect) {
device.scissor(rect)
}
// Pipeline component definitions
var default_depth_state = {
compare: "always", // never/less/equal/less_equal/greater/not_equal/greater_equal/always
test: false,
write: false,
bias: 0,
bias_slope_scale: 0,
bias_clamp: 0
}
var default_stencil_state = {
compare: "always", // never/less/equal/less_equal/greater/neq/greq/always
fail: "keep", // keep/zero/replace/incr_clamp/decr_clamp/invert/incr_wrap/decr_wrap
depth_fail: "keep",
pass: "keep"
}
var disabled_blend_state = {
enabled: false,
src_rgb: "zero",
dst_rgb: "zero",
op_rgb: "add",
src_alpha: "one",
dst_alpha: "zero",
op_alpha: "add"
}
var alpha_blend_state = {
enabled: true,
src_rgb: "src_alpha",
dst_rgb: "one_minus_src_alpha",
op_rgb: "add",
src_alpha: "one",
dst_alpha: "zero",
op_alpha: "add"
}
var default_multisample_state = {
count: 1,
mask: 0xFFFFFFFF,
domask: false
}
// Helper function to create pipeline config
function create_pipeline_config(options) {
var config = {
vertex: options.vertex,
fragment: options.fragment,
primitive: options.primitive || "triangle",
fill: options.fill ?? true,
depth: options.depth || default_depth_state,
stencil: {
enabled: options.stencil_enabled ?? false,
front: options.stencil_front || default_stencil_state,
back: options.stencil_back || default_stencil_state,
test: options.stencil_test ?? false,
compare_mask: options.stencil_compare_mask ?? 0,
write_mask: options.stencil_write_mask ?? 0
},
blend: options.blend || disabled_blend_state,
cull: options.cull || "none",
face: options.face || "cw",
alpha_to_coverage: options.alpha_to_coverage ?? false,
multisample: options.multisample || default_multisample_state,
label: options.label || "pipeline",
target: options.target || {}
}
// Ensure target has required properties
if (!config.target.color_targets) {
config.target.color_targets = [{
format: "rgba8",
blend: config.blend
}]
}
return config
}
var gameactor
var images = {}
var renderer_commands = []
var win_size = {width:500,height:500}
var logical = {width:500,height:500}
function get_img_gpu(img)
{
if (img.gpu) return img.gpu
var surf = new surface(img.cpu)
img.gpu = device.load_texture(surf, 0)
return img.gpu
}
///// input /////
var input_cb
var input_rate = 1/60
function poll_input() {
var evs = input.get_events()
// Filter and transform events
if (Array.isArray(evs)) {
var filteredEvents = []
// var wantMouse = imgui.wantmouse()
// var wantKeys = imgui.wantkeys()
var wantMouse = false
var wantKeys = false
for (var i = 0; i < evs.length; i++) {
var event = evs[i]
var shouldInclude = true
// Filter mouse events if ImGui wants mouse input
if (wantMouse && (event.type == 'mouse_motion' ||
event.type == 'mouse_button_down' ||
event.type == 'mouse_button_up' ||
event.type == 'mouse_wheel')) {
shouldInclude = false
}
// Filter keyboard events if ImGui wants keyboard input
if (wantKeys && (event.type == 'key_down' ||
event.type == 'key_up' ||
event.type == 'text_input' ||
event.type == 'text_editing')) {
shouldInclude = false
}
if (shouldInclude) {
// Transform mouse coordinates from window to renderer coordinates
if (event.pos && (event.type == 'mouse_motion' ||
event.type == 'mouse_button_down' ||
event.type == 'mouse_button_up' ||
event.type == 'mouse_wheel')) {
// Convert window coordinates to renderer logical coordinates
// var logicalPos = renderer.coordsFromWindow(event.pos)
// event.pos = logicalPos
}
// Handle drop events which also have position
if (event.pos && (event.type == 'drop_file' ||
event.type == 'drop_text' ||
event.type == 'drop_position')) {
// var logicalPos = renderer.coordsFromWindow(event.pos)
// event.pos = logicalPos
}
// Handle window events
if (event.type == 'window_pixel_size_changed') {
win_size.width = event.width
win_size.height = event.height
}
if (event.type == 'quit')
$_.stop()
if (event.type.includes('key')) {
if (event.key)
event.key = input.keyname(event.key)
}
if (event.type.startsWith('mouse_') && event.pos && event.pos.y)
event.pos.y = -event.pos.y + logical.height
filteredEvents.push(event)
}
}
evs = filteredEvents
}
input_cb(evs)
$_.delay(poll_input, input_rate)
}
prosperon.input = function(fn)
{
input_cb = fn
poll_input()
}
var sprite_pipeline = {
vertex: "sprite.vert",
fragment: "sprite.frag",
cull: "none",
target: {
color_targets: [
{format: device.swapchain_format(window), blend:disabled_blend_state}
],
},
vertex_buffer_descriptions: [ { slot:0, input_rate: "vertex", instance_step_rate: 0,
pitch: 8},
{slot:1, input_rate:"vertex", instance_step_rate: 0, pitch: 8},
{slot:2, input_rate:"vertex", instance_step_rate: 0, pitch: 16}
],
vertex_attributes: [
{ location: 0, buffer_slot: 0, format: "float2", offset: 0},
{ location: 1, buffer_slot: 1, format: "float2", offset: 0},
{ location: 2, buffer_slot: 2, format: "float4", offset: 0}
],
primitive: "triangle",
blend: alpha_blend_state
}
var pipey = load_pipeline(sprite_pipeline)
prosperon.create_batch = function create_batch(draw_cmds, done) {
var img = graphics.texture("pockle")
var pipeline = pipey
var geom = geometry.make_rect_quad({x:-320,y:-180,width:640,height:360})
geom.indices = geometry.make_quad_indices(1)
var cmd_buffer = device.acquire_cmd_buffer()
cmd_buffer.push_vertex_uniform_data(0, Pblob)
var pos_buffer = new sdl_gpu.buffer(device, {
vertex: true,
size: geom.xy.length/8
})
var uv_buffer = new sdl_gpu.buffer(device, {
vertex: true,
size: geom.uv.length/8
})
var color_buffer = new sdl_gpu.buffer(device, {
vertex: true,
size: geom.color.length/8
})
var index_buffer = new sdl_gpu.buffer(device, {
index: true,
size: geom.indices.length/8
})
var cpy_pass = cmd_buffer.copy_pass()
upload(cpy_pass, pos_buffer, geom.xy)
upload(cpy_pass, uv_buffer, geom.uv)
upload(cpy_pass, color_buffer, geom.color)
upload(cpy_pass, index_buffer, geom.indices)
if (!img.gpu) {
img.gpu = new sdl_gpu.texture(device, {
width: img.width,
height: img.height,
layers: 1,
mip_levels: 1,
samples: 0,
type: "2d",
format: "rgba8",
sampler: true,
})
var tbuf = new sdl_gpu.transfer_buffer(device, {
size: img.cpu.pixels.length/8,
usage: "upload"
})
tbuf.copy_blob(device, img.cpu.pixels)
cpy_pass.upload_to_texture({
transfer_buffer: tbuf,
offset: 0,
pixels_per_row: img.cpu.width,
rows_per_lay: img.cpu.height,
}, {
texture: img.gpu,
mip_level: 0,
layer: 0,
x: 0, y: 0, z: 0,
w: img.cpu.width,
h: img.cpu.height,
d: 1
}, false);
}
cpy_pass.end()
var pass = cmd_buffer.swapchain_pass(window)
pass.viewport({
x: 0, y: 0,
width: 640,
height: 360
})
pass.bind_pipeline(pipeline)
pass.bind_buffers(0, [{buffer:pos_buffer,offset:0}, {buffer:uv_buffer, offset:0}, {buffer:color_buffer, offset:0}])
pass.bind_index_buffer({buffer:index_buffer,offset:0}, 16)
pass.bind_samplers(false, 0, [{texture:img.gpu, sampler:std_sampler}])
pass.draw_indexed(6, 1, 0, 0, 0)
pass.end()
cmd_buffer.submit()
if (done) done()
}
////////// dmon hot reload ////////
function poll_file_changes() {
dmon.poll(e => {
if (e.action == 'modify' || e.action == 'create') {
// Check if it's an image file
var ext = e.file.split('.').pop().toLowerCase()
var imageExts = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tga', 'webp', 'qoi', 'ase', 'aseprite']
if (imageExts.includes(ext)) {
// Try to find the full path for this image
var possiblePaths = [
e.file,
e.root + e.file,
res.find_image(e.file.split('/').pop().split('.')[0])
].filter(p => p)
for (var path of possiblePaths) {
graphics.tex_hotreload(path)
}
}
}
})
// Schedule next poll in 0.5 seconds
$_.delay(poll_file_changes, 0.5)
}
var dmon = use('dmon')
prosperon.dmon = function()
{
dmon.watch('.')
poll_file_changes()
}
var window_cmds = {
size(size) {
window.size = size
},
}
prosperon.set_window = function(config)
{
for (var c in config)
if (window_cmds[c]) window_cmds[c](config[c])
}
prosperon.set_renderer = function(config)
{
}
// Function to load textures directly to the renderer
prosperon.load_texture = function(surface_data) {
var surf = new surface(surface_data)
if (!surf) return null
var tex = renderer.load_texture(surf)
if (!tex) return null
// Set pixel mode to nearest for all textures
tex.scaleMode = "nearest"
var tex_id = allocate_id()
resources.texture[tex_id] = tex
return {
id: tex_id,
texture: tex,
width: tex.width,
height: tex.height
}
}
return prosperon