diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..70e34ec --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "C_Cpp.errorSquiggles": "disabled" +} \ No newline at end of file diff --git a/examples/fond noir.jpg b/examples/fond noir.jpg new file mode 100644 index 0000000..75aab43 Binary files /dev/null and b/examples/fond noir.jpg differ diff --git a/examples/fond noir.jpg.import b/examples/fond noir.jpg.import new file mode 100644 index 0000000..cd1ea10 --- /dev/null +++ b/examples/fond noir.jpg.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d4famubcuvx74" +path="res://.godot/imported/fond noir.jpg-b123a7c5f65be3141c776d27ac42158f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://examples/fond noir.jpg" +dest_files=["res://.godot/imported/fond noir.jpg-b123a7c5f65be3141c776d27ac42158f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/examples/tron/Node2D.tscn b/examples/tron/Node2D.tscn new file mode 100644 index 0000000..dddd384 --- /dev/null +++ b/examples/tron/Node2D.tscn @@ -0,0 +1,19 @@ +[gd_scene load_steps=3 format=3 uid="uid://u25v40csc7a8"] + +[ext_resource type="Script" path="res://examples/tron/tron.gd" id="1_pd81r"] +[ext_resource type="Texture2D" uid="uid://d4famubcuvx74" path="res://examples/fond noir.jpg" id="2_2x2pa"] + +[node name="Node2d" type="Node2D"] + +[node name="ComputeShaderStudio2D" type="Node" parent="." node_paths=PackedStringArray("data")] +script = ExtResource("1_pd81r") +WSX = 800 +WSY = 450 +glsl_file = "res://examples/tron/tron.cpp" +GLSL_code = "" +data = [NodePath("../FondNoir"), null, null, null, null] + +[node name="FondNoir" type="Sprite2D" parent="."] +position = Vector2(400, 225.55) +scale = Vector2(1, 1.00644) +texture = ExtResource("2_2x2pa") diff --git a/examples/tron/tron.cpp b/examples/tron/tron.cpp new file mode 100644 index 0000000..41b39aa --- /dev/null +++ b/examples/tron/tron.cpp @@ -0,0 +1,302 @@ +// 800 * 450 +// data_0 : affichage +// data_1 : motos +// data_2 : faisceaux motos + +/* + * Couleurs Motos : + * Rouge : 0xFF0000FF + * Bleu : 0xFFFF0000 + */ + +/* + * Couleurs faisceaux : + * Rouge modifie: 0xFF0055FF + * Bleu modifie: 0xFF55FF00 + */ + +#define CLEAR 0xFF000000 +#define MOTO_1 0xFF0000FF +#define MOTO_2 0xFFFF0000 +#define BEAM_1 0xFF00FFFF +#define BEAM_2 0xFF55FF00 + +#define Display data_0 + +#define Motorcycles data_1 + +#define Beams data_2 + +#define Init 0 + +const int dimMoto = 10; + +// fonction random +float random(vec2 uv) +{ + return fract(sin(dot(uv.xy + random_time * 0.00001, + vec2(12.9898f, 78.233f))) * + 43758.5453123f); +} + +void initGame(ivec2 pos) +{ + uint p = pos.x + pos.y * WSX; + Display[p] = CLEAR; + Motorcycles[p] = CLEAR; + Beams[p] = CLEAR; + + // On choisis aleatoirement une position dans la partie gauche de la simulation + float randomY1 = random(vec2(123.45f, 67.89f)) * (WSY - 2 * dimMoto) + dimMoto; + float randomX1 = random(vec2(234.56f, 78.90f)) * (WSX / 2 - 2 * dimMoto) + dimMoto; + + ivec2 moto1Pos = ivec2(int(randomX1), int(randomY1)); + + // On choisis aleatoirement une position dans la partie droite de la simulation + float randomY2 = random(vec2(345.67f, 89.01f)) * (WSY - 2 * dimMoto) + dimMoto; + float randomX2 = random(vec2(456.78f, 90.12f)) * (WSX / 2 - 2 * dimMoto) + (WSX / 2); + + ivec2 moto2Pos = ivec2(int(randomX2), int(randomY2)); + + // pour que les motos soit spawn alignees pour la suite des mouvements + moto1Pos = ivec2((moto1Pos.x / dimMoto) * dimMoto, (moto1Pos.y / dimMoto) * dimMoto); + moto2Pos = ivec2((moto2Pos.x / dimMoto) * dimMoto, (moto2Pos.y / dimMoto) * dimMoto); + + // Premiere moto + if (pos.x >= moto1Pos.x && pos.x < moto1Pos.x + dimMoto && + pos.y >= moto1Pos.y && pos.y < moto1Pos.y + dimMoto) + Motorcycles[p] = MOTO_1; + + // deuxieme moto + if (pos.x >= moto2Pos.x && pos.x < moto2Pos.x + dimMoto && + pos.y >= moto2Pos.y && pos.y < moto2Pos.y + dimMoto) + Motorcycles[p] = MOTO_2; +} +bool isDirectionClear(ivec2 basePos, int direction) +{ + ivec2 nextPos = basePos; + + switch (direction) + { + case 0: + nextPos.y -= dimMoto; + break; // haut + case 1: + nextPos.x += dimMoto; + break; // droite + case 2: + nextPos.y += dimMoto; + break; // bas + case 3: + nextPos.x -= dimMoto; + break; // gauche + } + + // check limites + if (nextPos.x < 0 || nextPos.x + dimMoto > WSX || + nextPos.y < 0 || nextPos.y + dimMoto > WSY - 2) + { + return false; + } + + // check si prochain emplacement deja rempli + for (int j = 0; j < dimMoto; j++) + { + for (int i = 0; i < dimMoto; i++) + { + ivec2 checkPos = nextPos + ivec2(i, j); + uint checkP = checkPos.x + checkPos.y * WSX; + + if (Motorcycles[checkP] != CLEAR || Beams[checkP] != CLEAR) + { + return false; + } + } + } + + return true; +} + +int chooseDirection(ivec2 basePos, int id_moto, int randomDir) +{ + // Trouver la position de l'autre moto + ivec2 enemyPos = ivec2(-1, -1); + int enemyMoto = (id_moto == MOTO_1) ? MOTO_2 : MOTO_1; + + for (int y = 0; y < WSY; y++) + { + for (int x = 0; x < WSX; x++) + { + uint p = x + y * WSX; + if (Motorcycles[p] == enemyMoto) + { + enemyPos = ivec2(x, y); + break; + } + } + if (enemyPos.x != -1) + break; + } + + // Si on trouve l'autre moto, on calcule la direction ver elle + if (enemyPos.x != -1) + { + ivec2 diff = enemyPos - basePos; + + // nombre aleatoire pour decider si on suit l'ennemi ou si on fait un mouvement aleatoire + float chaseChance = random(vec2(float(step) * 0.7234f + float(id_moto), step)); + + // 70% de chance de suivre l'autre moto', 30% de faire un mouvement aleatoire + if (chaseChance < 0.70f) + { + int dirTab[4]; + float dirChoice = random(vec2(float(step) * 0.3456f + float(id_moto), step)); + + if (abs(diff.x) > abs(diff.y) || dirChoice > 0.5f) + { + dirTab[0] = (diff.x > 0) ? 1 : 3; // Droite ou Gauche + dirTab[1] = (diff.y > 0) ? 2 : 0; // Bas ou Haut + dirTab[2] = (random(vec2(step * 0.1234f, id_moto)) > 0.5f) ? 0 : 2; // Y aleatoire + dirTab[3] = (random(vec2(step * 0.5678f, id_moto)) > 0.5f) ? 1 : 3; // X aleatoire + } + else + { + dirTab[0] = (diff.y > 0) ? 2 : 0; // Bas ou Haut + dirTab[1] = (diff.x > 0) ? 1 : 3; // Droite ou Gauche + dirTab[2] = (random(vec2(step * 0.9012f, id_moto)) > 0.5f) ? 1 : 3; // X aleatoire + dirTab[3] = (random(vec2(step * 0.3456f, id_moto)) > 0.5f) ? 2 : 0; // Y aleatoire + } + + // Essayer chaque direction dans l'ordre de preference + for (int i = 0; i < 4; i++) + { + if (isDirectionClear(basePos, dirTab[i])) + { + return dirTab[i]; + } + } + } + } + + if (isDirectionClear(basePos, randomDir)) + { + return randomDir; + } + + // essayer toutes les directions dans un ordre aleatoire + int essais[4] = {0, 1, 2, 3}; + for (int i = 3; i > 0; i--) + { + int j = int(random(vec2(float(step) * 0.8901f + float(i), id_moto)) * float(i + 1)); + int temp = essais[i]; + essais[i] = essais[j]; + essais[j] = temp; + } + + for (int i = 0; i < 4; i++) + { + if (isDirectionClear(basePos, essais[i])) + { + return essais[i]; + } + } + + return -1; +} + +void moveMotorcyle(int id_moto) +{ + ivec2 pos = ivec2(gl_GlobalInvocationID.xy); + uint p = pos.x + pos.y * WSX; + + if (Motorcycles[p] != id_moto) + return; + + ivec2 basePos = ivec2((pos.x / dimMoto) * dimMoto, (pos.y / dimMoto) * dimMoto); + if (pos.x != basePos.x || pos.y != basePos.y) + return; // check coin superieur gauche + + // choix direction aleatoire + float seed = random(vec2(float(step) * (id_moto == MOTO_1 ? 0.125445f : 0.68419f), step)); // choix arbitraire des valeurs, peut largement mieux faire + int randomDir = int(seed * 4.0f); // 0: haut, 1: droite, 2: bas, 3: gauche + + int direction = chooseDirection(basePos, id_moto, randomDir); + + // si on trouve une direction valide + if (direction >= 0) + { + // calcul nouvelle position + ivec2 newPos = basePos; + switch (direction) + { + case 0: + newPos.y -= dimMoto; + break; // haut + case 1: + newPos.x += dimMoto; + break; // droite + case 2: + newPos.y += dimMoto; + break; // bas + case 3: + newPos.x -= dimMoto; + break; // gauche + } + + // efface avant d'avancer + for (int j = 0; j < dimMoto; j++) + { + for (int i = 0; i < dimMoto; i++) + { + ivec2 oldPos = basePos + ivec2(i, j); + uint oldP = oldPos.x + oldPos.y * WSX; + Motorcycles[oldP] = CLEAR; + Beams[oldP] = (id_moto == MOTO_1) ? BEAM_1 : BEAM_2; + } + } + + // la moto avance + for (int j = 0; j < dimMoto; j++) + { + for (int i = 0; i < dimMoto; i++) + { + ivec2 newPixelPos = newPos + ivec2(i, j); + uint newP = newPixelPos.x + newPixelPos.y * WSX; + Motorcycles[newP] = id_moto; + } + } + } +} + +void main() +{ + ivec2 pos = ivec2(gl_GlobalInvocationID.xy); + uint p = pos.x + pos.y * WSX; + + int slowFactor = 2; + + if (step == Init) + { + initGame(pos); + } + + if (step > 1 && step % slowFactor == 0) + { + if (Motorcycles[p] == MOTO_1) + { + moveMotorcyle(MOTO_1); + } + else if (Motorcycles[p] == MOTO_2) + { + moveMotorcyle(MOTO_2); + } + } + if (step > 1 && step % slowFactor == 1) + { + Display[p] = Motorcycles[p] + Beams[p]; + } + if (mouse_button > 0 && step > 100) + { + initGame(pos); + } +} \ No newline at end of file diff --git a/examples/tron/tron.gd b/examples/tron/tron.gd new file mode 100644 index 0000000..1f9f5e5 --- /dev/null +++ b/examples/tron/tron.gd @@ -0,0 +1,346 @@ +extends Node +class_name tron + +var current_pass : int = 0 + +# Put your GLSL code in the GLSL_main string below +# Here are all the accessible variables (uniforms) inside your GLSL code: +# uint x,y : from GlobalInvocationID.x and .y +# uint p : the position [x][y] in the invocation +# uint WSX,WSY : the Global WorkSpace of invocations (generally match the data size) +# int* data_0, data_1, etc : are the data treated (can be displayed by Sprite2D, TextureRect, etc). +# Access them by data_0[p], data_1[p], etc +# uint step : simulation step of the execution. Incresed by 1 after nb_passes +# uint nb_passes: the number of passes your code needs (by step). +# There is a barrier between each pass. +# uint current_pass: pass currently executed (one pass per frame, nb_passes by step) + +#region ComputeShaderStudio + +var GLSL_header = """ +#version 450 + +// Invocations in the (x, y, z) dimension +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +// Bindings to the buffers we create in our script +layout(binding = 0) buffer Params { + int step; + int current_pass; + int mousex; + int mousey; + int mouse_button; + int random_time; +}; + +""" + +## Print the current step. +@export var print_step:bool = false +## Print the current pass. +@export var print_passes:bool = false +## Print in Output all the generated code. +## Can be usefull for debugging or to understand all the GLSL code needed. +@export var print_generated_code:bool = false +## Do not execute compute shader at launch. +@export var pause:bool = false +## Number of passes into each execution step. +## Between two passes, your GLSL code is synchronized. +@export var nb_passes : int = 1 +## Workspace Size X, usually it matches the x size of your image (Sprite2D or TextureRect) +@export var WSX : int = 128 +## Workspace Size Y, usually it matches the y size of your image (Sprite2D or TextureRect) +@export var WSY : int = 128 + +## Drag & drop your external GLSL file here (use .cpp for your source file extension) +@export_file("*.cpp") var glsl_file: String +## Write your GLSL code just below or use an external file above +@export_multiline var GLSL_code : String = """ +// Write your code HERE +void main() { + uint x = gl_GlobalInvocationID.x; + uint y = gl_GlobalInvocationID.y; + uint p = x + y * WSX; + data_0[p] = 0xFFF00FFF - int(p)*(step+1); + data_1[p] = 0xFF0000AA + int( 1.0 + 99999.9*sin(float(x+float(step+y))/1000.0)); +} +""" +## Drag and drop here your Sprite2D or TextureRect. +@export var data:Array[Node] + +var rd : RenderingDevice +var shader : RID +var buffers : Array[RID] +var buffer_params : RID +var buffer_user : RID + +var uniforms : Array[RDUniform] +#var uniform_2 : RDUniform +var uniform_params : RDUniform +var uniform_user : RDUniform + +var uniform_user_data : PackedByteArray = PackedByteArray([0]) + +var bindings : Array = [] + +var pipeline : RID +var uniform_set : RID + +# Called when the node enters the scene tree for the first time. +#region _ready +func _ready(): + compile() + +func compile(): + # Create a local rendering device. + rd = RenderingServer.create_local_rendering_device() + if not rd: + set_process(false) + printerr("Compute shaders are not available") + return + + # ********************* + # * SHADER CREATION * + # ********************* + + var nb_buffers : int = data.size() + + # Create GLSL Header + GLSL_header += """ +uint WSX="""+str(WSX)+""";"""+""" +uint WSY="""+str(WSY)+"""; +""" + + for i in nb_buffers: + GLSL_header += """ +layout(binding = """+str(i+2)+""") buffer Data"""+str(i)+""" { + int data_"""+str(i)+"""[]; +}; + +""" + + # The external GLSL file takes priority + if glsl_file != "": + print("Load the GLSL file:" + glsl_file ) + GLSL_code = load_glsl_file(glsl_file) + var GLSL_all : String = GLSL_header + GLSL_code + if print_generated_code == true: + print(GLSL_all) + + # Compile the shader by passing a string + var shader_src := RDShaderSource.new() + shader_src.set_stage_source(RenderingDevice.SHADER_STAGE_COMPUTE, GLSL_all) + var shader_spirv := rd.shader_compile_spirv_from_source(shader_src) + + var err:String=shader_spirv.compile_error_compute + + if err != "": + printerr(err) + get_tree().quit() + + shader = rd.shader_create_from_spirv(shader_spirv) + + + # ********************* + # * BUFFERS CREATION * + # ********************* + + # Buffer for current_pass + var input_params :PackedInt32Array = PackedInt32Array() + input_params.append(step) + input_params.append(current_pass) + var input_params_bytes := input_params.to_byte_array() + buffer_params = rd.storage_buffer_create(input_params_bytes.size(), input_params_bytes) + buffer_user = rd.storage_buffer_create(uniform_user_data.size(), uniform_user_data) + + # Creation of nb_buffers Buffers of type Int32 + for b in nb_buffers: + var input :PackedInt32Array = PackedInt32Array() + for i in range(WSX): + for j in range(WSY): + input.append(randi()) + var input_bytes :PackedByteArray = input.to_byte_array() + buffers.append(rd.storage_buffer_create(input_bytes.size(), input_bytes)) + + # ********************* + # * UNIFORMS CREATION * + # ********************* + + # Create current_pass uniform pass + uniform_params = RDUniform.new() + uniform_params.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER + uniform_params.binding = 0 # this needs to match the "binding" in our shader file + uniform_params.add_id(buffer_params) + + # Create current_pass uniform pass + uniform_user = RDUniform.new() + uniform_user.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER + uniform_user.binding = 1 # this needs to match the "binding" in our shader file + uniform_user.add_id(buffer_user) + + var nb_uniforms : int = data.size() + for b in nb_uniforms: + var uniform = RDUniform.new() + uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER + uniform.binding = b+2 # this needs to match the "binding" in our shader file + uniform.add_id(buffers[b]) + uniforms.append(uniform) + + # Create the uniform SET between CPU & GPU + bindings = [uniform_params, uniform_user] + for b in nb_buffers: + bindings.append(uniforms[b]) + + uniform_set = rd.uniform_set_create(bindings, shader, 0) # the last parameter (the 0) needs to match the "set" in our shader file + + # ************************** + # * COMPUTE LIST CREATION * + # ************************** + # Create a compute pipeline + pipeline = rd.compute_pipeline_create(shader) + +#endregion + +func load_glsl_file(file_name:String) -> String: + var file = FileAccess.open(file_name, FileAccess.READ) + if file == null: + printerr("Unable to load GLSL file:" + file_name) + return "void main() {}" + var src_glsl:String = file.get_as_text() + return src_glsl + +func display_all_values(): + # Read back the data from the buffers + for b in data.size(): + var output_bytes : PackedByteArray = rd.buffer_get_data(buffers[b]) + if is_instance_valid(data[b]): + display_values(data[b], output_bytes) + +func display_values(disp : Node, values : PackedByteArray): # PackedInt32Array): + var img : Image = Image.create_from_data(WSX, WSY, false, Image.FORMAT_RGBA8, values) + var tex : Texture2D = ImageTexture.create_from_image(img) + + if disp is Sprite2D : + var old_width : float = disp.texture.get_width() + var old_height : float = disp.texture.get_height() + disp.set_texture(tex) + disp.scale *= Vector2(old_width/WSX, old_height/WSY) + + else : + disp.set_texture(tex) + + +var step : int = 0 + +func compute(): + if print_step == true && current_pass%nb_passes == 0: + print("Step="+str(step)) + if print_passes == true: + print(" CurrentPass="+str(current_pass)) + + _update_uniforms() + + # Prepare the Computer List ############################################ + var compute_list : int = rd.compute_list_begin() + rd.compute_list_bind_compute_pipeline(compute_list, pipeline) + rd.compute_list_bind_uniform_set(compute_list, uniform_set, 0) + rd.compute_list_dispatch(compute_list, WSX>>3, WSY>>3, 1) + rd.compute_list_end() + ####################################################################### + + # Submit to GPU and wait for sync + rd.submit() + rd.sync() + + # Update step and current_passe + current_pass = (current_pass + 1) % nb_passes + if current_pass == 0: + step += 1 + +func _process(_delta): + if pause == false: + compute() + display_all_values() + +## Pass the interesting values from CPU to GPU +func _update_uniforms(): + var input_params : PackedInt32Array = PackedInt32Array() + + input_params.append(step) + input_params.append(current_pass) + + var pos : Vector2 = screen_to_data0(get_viewport().get_mouse_position()) + input_params.append(pos.x) + input_params.append(pos.y) + + + # Mouse button + input_params.append(Input.get_mouse_button_mask()) + # var left_pressed : bool = Input.is_action_just_pressed("mouse_click") + # input_params.append(left_pressed) + var time : int =Time.get_ticks_usec() + input_params.append(time) + + + var input_params_bytes := input_params.to_byte_array() + buffer_params = rd.storage_buffer_create(input_params_bytes.size(), input_params_bytes) + uniform_params = RDUniform.new() + uniform_params.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER + uniform_params.binding = 0 # this needs to match the "binding" in our shader file + uniform_params.add_id(buffer_params) + bindings[0] = uniform_params + + buffer_user = rd.storage_buffer_create(uniform_user_data.size(), uniform_user_data) + uniform_user = RDUniform.new() + uniform_user.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER + uniform_user.binding = 1 # this needs to match the "binding" in our shader file + uniform_user.add_id(buffer_user) + bindings[1] = uniform_user + + uniform_set = rd.uniform_set_create(bindings, shader, 0) + # Note: when changing the uniform set, use the same bindings Array (do not create a new Array) + +## Cleaning up the GPU resources +func _notification(what): + # Object destructor, triggered before the engine deletes this Node. + if what == NOTIFICATION_PREDELETE: + cleanup_gpu() + +func cleanup_gpu(): + if rd == null: + return + # All resources must be freed after use to avoid memory leaks. + rd.free_rid(pipeline) + pipeline = RID() + + rd.free_rid(uniform_set) + uniform_set = RID() + + rd.free_rid(shader) + shader = RID() + + rd.free() + rd = null + +#endregion + +func _on_button_step(): + pause = true + compute() + display_all_values() + + +func _on_button_play(): + pause = false # Replace with function body. + +func screen_to_data0(pos : Vector2): + if data.size() <= 0 : + return Vector2(0, 0) + + if data[0] is Sprite2D: + var sprite : Sprite2D = data[0] + pos.x = (pos.x - sprite.position.x) / sprite.scale.x + WSX/2 + pos.y = (pos.y - sprite.position.y) / sprite.scale.y + WSY/2 + return pos; + else: + return Vector2(0,0) diff --git a/examples/tron/tron.gd.uid b/examples/tron/tron.gd.uid new file mode 100644 index 0000000..619133e --- /dev/null +++ b/examples/tron/tron.gd.uid @@ -0,0 +1 @@ +uid://bsnvapg1v3nl3 diff --git a/project.godot b/project.godot index 1617345..c21bced 100644 --- a/project.godot +++ b/project.godot @@ -12,9 +12,26 @@ config_version=5 config/name="compute_shader_studio" run/main_scene="res://examples/example_1.tscn" -config/features=PackedStringArray("4.4", "Forward Plus") +config/features=PackedStringArray("4.3") config/icon="res://addons/compute_shader_studio/icon.png" +[display] + +window/size/viewport_width=800 +window/size/viewport_height=450 + +[dotnet] + +project/assembly_name="compute_shader_studio" + [editor_plugins] enabled=PackedStringArray("res://addons/compute_shader_studio/plugin.cfg") + +[input] + +mouse_click={ +"deadzone": 0.5, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null) +] +}