620 lines
15 KiB
Plaintext
620 lines
15 KiB
Plaintext
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 render‐surface 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 world‑units from center to left/right?
|
||
// if zoom = 1 means 1 world‑unit == 1 pixel:
|
||
def halfW = vpW * 0.5;
|
||
def halfH = vpH * 0.5;
|
||
|
||
// define your clipping‐volume in camera‑space:
|
||
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 “zero‐to‐one” 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
|