Add a compute shader demo (#810)

Co-authored-by: dzil123 <5725958+dzil123@users.noreply.github.com>
Co-authored-by: Hugo Locurcio <hugo.locurcio@hugo.pro>
Co-authored-by: MoltenCoffee <13321277+MoltenCoffee@users.noreply.github.com>
This commit is contained in:
Hugo Locurcio
2023-01-03 00:16:59 +01:00
committed by GitHub
parent d3cc6be62c
commit 85ca2fb2a1
10 changed files with 591 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
# Compute Shader Heightmap
This demo project gives an example of how to use *compute shaders* in Godot.
A compute shader is a piece of code that runs on the GPU and is written in GLSL
(as opposed to the
[Godot shader language](https://docs.godotengine.org/en/latest/tutorials/shaders/shader_reference/index.html)).
A compute shader can be used to take advantage of the GPU's ability to perform
massively parallel operations faster than a CPU. This demo can generate the
heightmap of an island from a noise texture, both on the CPU and the GPU. You
can try both options to compare the time it takes to generate the heightmap on
the CPU and GPU respectively.
For smaller noise textures, the CPU will often be faster, but the larger the
gains are by using the GPU. On a PC with a NVIDIA GeForce RTX 3060 and
11th-generation Intel Core i7 processor, the compute shader was tested to be
faster for textures 1024×1024 and larger.
The dimensions of the image can be set on the exported **Dimensions** property
on the main scene. By default, it's set to 2048, which creates a 2048×2048
heightmap.
> **Note**
>
> The shader code has been structured to be followed step-by-step by the user,
> and may not necessarily represent best practices. The CPU code is also less
> optimized than it could be. This is to reflect the GPU code as much as
> possible. Besides the use of the GPU, no multithreading is used.
Languages: GDScript, GLSL
Renderer: Forward Mobile
![Compute Shader Heightmap](screenshots/compute_shader_heightmap.webp)

View File

@@ -0,0 +1,61 @@
#[compute]
#version 460
// Instruct the GPU to use 8x8x1 = 64 local invocations per workgroup.
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
// Prepare memory for the image, which will be both read and written to
// `restrict` is used to tell the compiler that the memory will only be accessed
// by the `heightmap` variable.
layout(r8, binding = 0) restrict uniform image2D heightmap;
// `readonly` is used to tell the compiler that we will not write to this memory.
// This allows the compiler to make some optimizations it couldn't otherwise.
layout(rgba8, binding = 1) restrict readonly uniform image2D gradient;
// This function is the GPU counterpart of `compute_island_cpu()` in `main.gd`.
void main() {
// Grab the current pixel's position from the ID of this specific invocation ("thread").
ivec2 coords = ivec2(gl_GlobalInvocationID.xy);
ivec2 dimensions = imageSize(heightmap);
// Calculate the center of the image.
// Because we are working with integers ('round numbers') here,
// the result will be floored to an integer.
ivec2 center = dimensions / 2;
// Calculate the smallest distance from center to edge.
int smallest_radius = min(center.x, center.y);
// Calculate the distance from the center of the image to the current pixel.
float dist = distance(coords, center);
// Retrieve the range of the gradient image.
int gradient_max_x = imageSize(gradient).x - 1;
// Calculate the gradient index based on the distance from the center.
// `mix()` functions similarly to `lerp()` in GDScript.
int gradient_x = int(mix(0.0, float(gradient_max_x), dist / float(smallest_radius)));
// Retrieve the gradient value at the calculated position.
ivec2 gradient_pos = ivec2(gradient_x, 0);
vec4 gradient_color = imageLoad(gradient, gradient_pos);
// Even though the image format only has the red channel,
// this will still return a vec4: `vec4(red, 0.0, 0.0, 1.0)`
vec4 pixel = imageLoad(heightmap, coords);
// Multiply the pixel's red channel by the gradient's red channel
// (or any RGB channel, they're all the same except for alpha).
pixel.r *= gradient_color.r;
// If the pixel is below a certain threshold, this sets it to 0.0.
// The `step()` function is like `clamp()`, but it returns 0.0 if the value is
// below the threshold, or 1.0 if it is above.
//
// This is why we multiply it by the pixel's value again: to get the original
// value back if it is above the threshold. This shorthand replaces an `if`
// statement, which would cause branching and thus potentially slow down the
// shader.
pixel.r = step(0.2, pixel.r) * pixel.r;
// Store the pixel back into the image.
// WARNING: make sure you are writing to the same coordinate that you read from.
// If you don't, you may end up writing to a pixel, before that pixel is read
// by a different invocation and cause errors.
imageStore(heightmap, coords, pixel);
}

View File

@@ -0,0 +1,14 @@
[remap]
importer="glsl"
type="RDShaderFile"
uid="uid://cgowuwlmmweew"
path="res://.godot/imported/compute_shader.glsl-0816de56a4f9dce0934d88a29a0758cf.res"
[deps]
source_file="res://compute_shader.glsl"
dest_files=["res://.godot/imported/compute_shader.glsl-0816de56a4f9dce0934d88a29a0758cf.res"]
[params]

View File

@@ -0,0 +1 @@
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><g transform="translate(32 32)"><path d="m-16-32c-8.86 0-16 7.13-16 15.99v95.98c0 8.86 7.13 15.99 16 15.99h96c8.86 0 16-7.13 16-15.99v-95.98c0-8.85-7.14-15.99-16-15.99z" fill="#363d52"/><path d="m-16-32c-8.86 0-16 7.13-16 15.99v95.98c0 8.86 7.13 15.99 16 15.99h96c8.86 0 16-7.13 16-15.99v-95.98c0-8.85-7.14-15.99-16-15.99zm0 4h96c6.64 0 12 5.35 12 11.99v95.98c0 6.64-5.35 11.99-12 11.99h-96c-6.64 0-12-5.35-12-11.99v-95.98c0-6.64 5.36-11.99 12-11.99z" fill-opacity=".4"/></g><g stroke-width="9.92746" transform="matrix(.10073078 0 0 .10073078 12.425923 2.256365)"><path d="m0 0s-.325 1.994-.515 1.976l-36.182-3.491c-2.879-.278-5.115-2.574-5.317-5.459l-.994-14.247-27.992-1.997-1.904 12.912c-.424 2.872-2.932 5.037-5.835 5.037h-38.188c-2.902 0-5.41-2.165-5.834-5.037l-1.905-12.912-27.992 1.997-.994 14.247c-.202 2.886-2.438 5.182-5.317 5.46l-36.2 3.49c-.187.018-.324-1.978-.511-1.978l-.049-7.83 30.658-4.944 1.004-14.374c.203-2.91 2.551-5.263 5.463-5.472l38.551-2.75c.146-.01.29-.016.434-.016 2.897 0 5.401 2.166 5.825 5.038l1.959 13.286h28.005l1.959-13.286c.423-2.871 2.93-5.037 5.831-5.037.142 0 .284.005.423.015l38.556 2.75c2.911.209 5.26 2.562 5.463 5.472l1.003 14.374 30.645 4.966z" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 919.24059 771.67186)"/><path d="m0 0v-47.514-6.035-5.492c.108-.001.216-.005.323-.015l36.196-3.49c1.896-.183 3.382-1.709 3.514-3.609l1.116-15.978 31.574-2.253 2.175 14.747c.282 1.912 1.922 3.329 3.856 3.329h38.188c1.933 0 3.573-1.417 3.855-3.329l2.175-14.747 31.575 2.253 1.115 15.978c.133 1.9 1.618 3.425 3.514 3.609l36.182 3.49c.107.01.214.014.322.015v4.711l.015.005v54.325c5.09692 6.4164715 9.92323 13.494208 13.621 19.449-5.651 9.62-12.575 18.217-19.976 26.182-6.864-3.455-13.531-7.369-19.828-11.534-3.151 3.132-6.7 5.694-10.186 8.372-3.425 2.751-7.285 4.768-10.946 7.118 1.09 8.117 1.629 16.108 1.846 24.448-9.446 4.754-19.519 7.906-29.708 10.17-4.068-6.837-7.788-14.241-11.028-21.479-3.842.642-7.702.88-11.567.926v.006c-.027 0-.052-.006-.075-.006-.024 0-.049.006-.073.006v-.006c-3.872-.046-7.729-.284-11.572-.926-3.238 7.238-6.956 14.642-11.03 21.479-10.184-2.264-20.258-5.416-29.703-10.17.216-8.34.755-16.331 1.848-24.448-3.668-2.35-7.523-4.367-10.949-7.118-3.481-2.678-7.036-5.24-10.188-8.372-6.297 4.165-12.962 8.079-19.828 11.534-7.401-7.965-14.321-16.562-19.974-26.182 4.4426579-6.973692 9.2079702-13.9828876 13.621-19.449z" fill="#478cbf" transform="matrix(4.162611 0 0 -4.162611 104.69892 525.90697)"/><path d="m0 0-1.121-16.063c-.135-1.936-1.675-3.477-3.611-3.616l-38.555-2.751c-.094-.007-.188-.01-.281-.01-1.916 0-3.569 1.406-3.852 3.33l-2.211 14.994h-31.459l-2.211-14.994c-.297-2.018-2.101-3.469-4.133-3.32l-38.555 2.751c-1.936.139-3.476 1.68-3.611 3.616l-1.121 16.063-32.547 3.138c.015-3.498.06-7.33.06-8.093 0-34.374 43.605-50.896 97.781-51.086h.066.067c54.176.19 97.766 16.712 97.766 51.086 0 .777.047 4.593.063 8.093z" fill="#478cbf" transform="matrix(4.162611 0 0 -4.162611 784.07144 817.24284)"/><path d="m0 0c0-12.052-9.765-21.815-21.813-21.815-12.042 0-21.81 9.763-21.81 21.815 0 12.044 9.768 21.802 21.81 21.802 12.048 0 21.813-9.758 21.813-21.802" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 389.21484 625.67104)"/><path d="m0 0c0-7.994-6.479-14.473-14.479-14.473-7.996 0-14.479 6.479-14.479 14.473s6.483 14.479 14.479 14.479c8 0 14.479-6.485 14.479-14.479" fill="#414042" transform="matrix(4.162611 0 0 -4.162611 367.36686 631.05679)"/><path d="m0 0c-3.878 0-7.021 2.858-7.021 6.381v20.081c0 3.52 3.143 6.381 7.021 6.381s7.028-2.861 7.028-6.381v-20.081c0-3.523-3.15-6.381-7.028-6.381" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 511.99336 724.73954)"/><path d="m0 0c0-12.052 9.765-21.815 21.815-21.815 12.041 0 21.808 9.763 21.808 21.815 0 12.044-9.767 21.802-21.808 21.802-12.05 0-21.815-9.758-21.815-21.802" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 634.78706 625.67104)"/><path d="m0 0c0-7.994 6.477-14.473 14.471-14.473 8.002 0 14.479 6.479 14.479 14.473s-6.477 14.479-14.479 14.479c-7.994 0-14.471-6.485-14.471-14.479" fill="#414042" transform="matrix(4.162611 0 0 -4.162611 656.64056 631.05679)"/></g></svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://651nx11bd2yf"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
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
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@@ -0,0 +1,260 @@
extends Control
@export_file("*.glsl") var shader_file
@export_range(128, 4096, 1, "exp") var dimension: int = 512
@onready var seed_input: SpinBox = $CenterContainer/VBoxContainer/PanelContainer/VBoxContainer/GridContainer/SeedInput
@onready var heightmap_rect: TextureRect = $CenterContainer/VBoxContainer/PanelContainer2/VBoxContainer/GridContainer/RawHeightmap
@onready var island_rect: TextureRect = $CenterContainer/VBoxContainer/PanelContainer2/VBoxContainer/GridContainer/ComputedHeightmap
var noise: FastNoiseLite
var gradient: Gradient
var gradient_tex: GradientTexture1D
var po2_dimensions: int
var start_time: int
var rd: RenderingDevice
var shader_rid: RID
var heightmap_rid: RID
var gradient_rid: RID
var uniform_set: RID
var pipeline: RID
func _init() -> void:
# Create a noise function as the basis for our heightmap.
noise = FastNoiseLite.new()
noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH
noise.fractal_octaves = 5
noise.fractal_lacunarity = 1.9
# Create a gradient to function as overlay.
gradient = Gradient.new()
gradient.add_point(0.6, Color(0.9, 0.9, 0.9, 1.0))
gradient.add_point(0.8, Color(1.0, 1.0, 1.0, 1.0))
# The gradient will start black, transition to grey in the first 70%, then to white in the last 30%.
gradient.reverse()
# Create a 1D texture (single row of pixels) from gradient.
gradient_tex = GradientTexture1D.new()
gradient_tex.gradient = gradient
func _ready() -> void:
randomize_seed()
po2_dimensions = nearest_po2(dimension)
noise.frequency = 0.003 / (float(po2_dimensions) / 512.0)
# Append GPU and CPU model names to make performance comparison more informed.
# On unbalanced configurations where the CPU is much stronger than the GPU,
# compute shaders may not be beneficial.
$CenterContainer/VBoxContainer/PanelContainer/VBoxContainer/HBoxContainer/CreateButtonGPU.text += "\n" + RenderingServer.get_video_adapter_name()
$CenterContainer/VBoxContainer/PanelContainer/VBoxContainer/HBoxContainer/CreateButtonCPU.text += "\n" + OS.get_processor_name()
func _notification(what):
# Object destructor, triggered before the engine deletes this Node.
if what == NOTIFICATION_PREDELETE:
cleanup_gpu()
# Generate a random integer, convert it to a string and set it as text for the TextEdit field.
func randomize_seed() -> void:
seed_input.value = randi()
func prepare_image() -> Image:
start_time = Time.get_ticks_usec()
# Use the to_int() method on the String to convert to a valid seed.
noise.seed = seed_input.value
# Create image from noise.
var heightmap := noise.get_image(po2_dimensions, po2_dimensions, false, false)
# Create ImageTexture to display original on screen.
var clone = Image.new()
clone.copy_from(heightmap)
clone.resize(512, 512, Image.INTERPOLATE_NEAREST)
var clone_tex := ImageTexture.create_from_image(clone)
heightmap_rect.texture = clone_tex
return heightmap
func init_gpu() -> void:
# These resources are expensive to make, so create them once and cache for subsequent runs.
# Create a local rendering device (required to run compute shaders).
rd = RenderingServer.create_local_rendering_device()
if rd == null:
OS.alert("""Couldn't create local RenderingDevice on GPU: %s
Note: RenderingDevice is only available in the Forward Plus and Forward Mobile backends, not Compatibility.""" % RenderingServer.get_video_adapter_name())
return
# Prepare the shader.
shader_rid = load_shader(rd, shader_file)
# Create format for heightmap.
var heightmap_format := RDTextureFormat.new()
# There are a lot of different formats. It might take some studying to be able to be able to
# choose the right ones. In this case, we tell it to interpret the data as a single byte for red.
# Even though the noise image only has a luminance channel, we can just interpret this as if it
# was the red channel. The byte layout is the same!
heightmap_format.format = RenderingDevice.DATA_FORMAT_R8_UNORM
heightmap_format.width = po2_dimensions
heightmap_format.height = po2_dimensions
# The TextureUsageBits are stored as 'bit fields', denoting what can be done with the data.
# Because of how bit fields work, we can just sum the required ones: 8 + 64 + 128
heightmap_format.usage_bits = \
RenderingDevice.TEXTURE_USAGE_STORAGE_BIT + \
RenderingDevice.TEXTURE_USAGE_CAN_UPDATE_BIT + \
RenderingDevice.TEXTURE_USAGE_CAN_COPY_FROM_BIT
# Prepare heightmap texture. We will set the data later.
heightmap_rid = rd.texture_create(heightmap_format, RDTextureView.new())
# Create uniform for heightmap.
var heightmap_uniform := RDUniform.new()
heightmap_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE
heightmap_uniform.binding = 0 # This matches the binding in the shader.
heightmap_uniform.add_id(heightmap_rid)
# Create format for the gradient.
var gradient_format := RDTextureFormat.new()
# The gradient could have been converted to a single channel like we did with the heightmap,
# but for illustrative purposes, we use four channels (RGBA).
gradient_format.format = RenderingDevice.DATA_FORMAT_R8G8B8A8_UNORM
gradient_format.width = gradient_tex.width # Default: 256
# GradientTexture1D always has a height of 1.
gradient_format.height = 1
gradient_format.usage_bits = \
RenderingDevice.TEXTURE_USAGE_STORAGE_BIT + \
RenderingDevice.TEXTURE_USAGE_CAN_UPDATE_BIT
# Storage gradient as texture.
gradient_rid = rd.texture_create(gradient_format, RDTextureView.new(), [gradient_tex.get_image().get_data()])
# Create uniform for gradient.
var gradient_uniform := RDUniform.new()
gradient_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE
gradient_uniform.binding = 1 # This matches the binding in the shader.
gradient_uniform.add_id(gradient_rid)
uniform_set = rd.uniform_set_create([heightmap_uniform, gradient_uniform], shader_rid, 0)
pipeline = rd.compute_pipeline_create(shader_rid)
func compute_island_gpu(heightmap: Image) -> void:
if rd == null:
init_gpu()
if rd == null:
$CenterContainer/VBoxContainer/PanelContainer2/VBoxContainer/HBoxContainer2/Label2.text = \
"RenderingDevice is not available on the current rendering driver"
return
# Store heightmap as texture.
rd.texture_update(heightmap_rid, 0, heightmap.get_data())
var compute_list := rd.compute_list_begin()
rd.compute_list_bind_compute_pipeline(compute_list, pipeline)
rd.compute_list_bind_uniform_set(compute_list, uniform_set, 0)
# This is where the magic happens! As our shader has a work group size of 8x8x1, we dispatch
# one for every 8x8 block of pixels here. This ratio is highly tunable, and performance may vary.
rd.compute_list_dispatch(compute_list, po2_dimensions / 8, po2_dimensions / 8, 1)
rd.compute_list_end()
rd.submit()
# Wait for the GPU to finish.
# Normally, you would do this after a few frames have passed so the compute shader can run in the background.
rd.sync()
# Retrieve processed data.
var output_bytes := rd.texture_get_data(heightmap_rid, 0)
# Even though the GPU was working on the image as if each byte represented the red channel,
# we'll interpret the data as if it was the luminance channel.
var island_img := Image.create_from_data(po2_dimensions, po2_dimensions, false, Image.FORMAT_L8, output_bytes)
display_island(island_img)
func cleanup_gpu() -> void:
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(gradient_rid)
gradient_rid = RID()
rd.free_rid(heightmap_rid)
heightmap_rid = RID()
rd.free_rid(shader_rid)
shader_rid = RID()
rd.free()
rd = null
# Import, compile and load shader, return reference.
func load_shader(rd: RenderingDevice, path: String) -> RID:
var shader_file_data: RDShaderFile = load(path)
var shader_spirv: RDShaderSPIRV = shader_file_data.get_spirv()
return rd.shader_create_from_spirv(shader_spirv)
func compute_island_cpu(heightmap: Image) -> void:
# This function is the CPU counterpart of the `main()` function in `compute_shader.glsl`.
var center := Vector2i(po2_dimensions, po2_dimensions) / 2
# Loop over all pixel coordinates in the image.
for y in range(0, po2_dimensions):
for x in range(0, po2_dimensions):
var coord := Vector2i(x, y)
var pixel := heightmap.get_pixelv(coord)
# Calculate the distance between the coord and the center.
var distance := Vector2(center).distance_to(Vector2(coord))
# As the X and Y dimensions are the same, we can use center.x as a proxy for the distance
# from the center to an edge.
var gradient_color := gradient.sample(distance / float(center.x))
# We use the v ('value') of the pixel here. This is not the same as the luminance we use
# in the compute shader, but close enough for our purposes here.
pixel.v *= gradient_color.v
if pixel.v < 0.2:
pixel.v = 0.0
heightmap.set_pixelv(coord, pixel)
display_island(heightmap)
func display_island(island: Image) -> void:
# Create ImageTexture to display original on screen.
var island_tex := ImageTexture.create_from_image(island)
island_rect.texture = island_tex
# Calculate and display elapsed time.
var stop_time := Time.get_ticks_usec()
var elapsed := stop_time - start_time
$CenterContainer/VBoxContainer/PanelContainer2/VBoxContainer/HBoxContainer/Label2.text = "%s ms" % str(elapsed * 0.001).pad_decimals(1)
func _on_random_button_pressed() -> void:
randomize_seed()
func _on_create_button_gpu_pressed() -> void:
var heightmap = prepare_image()
call_deferred("compute_island_gpu", heightmap)
func _on_create_button_cpu_pressed() -> void:
var heightmap = prepare_image()
call_deferred("compute_island_cpu", heightmap)

View File

@@ -0,0 +1,158 @@
[gd_scene load_steps=4 format=3 uid="uid://dm8doenormt4l"]
[ext_resource type="Script" path="res://main.gd" id="1_r4h6n"]
[sub_resource type="Gradient" id="Gradient_l3ffd"]
colors = PackedColorArray(0, 0.686275, 0.658824, 1, 0.542081, 0.741499, 1, 1)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_uwj8q"]
gradient = SubResource("Gradient_l3ffd")
fill_to = Vector2(1, 1)
[node name="Main" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_r4h6n")
shader_file = "res://compute_shader.glsl"
[node name="Background" type="TextureRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
texture = SubResource("GradientTexture2D_uwj8q")
[node name="CenterContainer" type="CenterContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="PanelContainer" type="PanelContainer" parent="CenterContainer/VBoxContainer"]
custom_minimum_size = Vector2(250, 0)
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/VBoxContainer/PanelContainer"]
layout_mode = 2
theme_override_constants/separation = 8
[node name="TitleLabel" type="Label" parent="CenterContainer/VBoxContainer/PanelContainer/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 20
text = "Create Island"
horizontal_alignment = 1
vertical_alignment = 2
[node name="HSeparator" type="HSeparator" parent="CenterContainer/VBoxContainer/PanelContainer/VBoxContainer"]
layout_mode = 2
[node name="GridContainer" type="HBoxContainer" parent="CenterContainer/VBoxContainer/PanelContainer/VBoxContainer"]
layout_mode = 2
alignment = 1
[node name="SeedLabel" type="Label" parent="CenterContainer/VBoxContainer/PanelContainer/VBoxContainer/GridContainer"]
layout_mode = 2
size_flags_vertical = 1
text = "Seed"
horizontal_alignment = 2
vertical_alignment = 1
[node name="SeedInput" type="SpinBox" parent="CenterContainer/VBoxContainer/PanelContainer/VBoxContainer/GridContainer"]
custom_minimum_size = Vector2(140, 35)
layout_mode = 2
max_value = 1e+12
[node name="RandomButton" type="Button" parent="CenterContainer/VBoxContainer/PanelContainer/VBoxContainer/GridContainer"]
layout_mode = 2
text = "Random"
[node name="HBoxContainer" type="HBoxContainer" parent="CenterContainer/VBoxContainer/PanelContainer/VBoxContainer"]
layout_mode = 2
alignment = 1
[node name="CreateButtonGPU" type="Button" parent="CenterContainer/VBoxContainer/PanelContainer/VBoxContainer/HBoxContainer"]
custom_minimum_size = Vector2(0, 54)
layout_mode = 2
size_flags_horizontal = 3
text = "Create (GPU)"
[node name="CreateButtonCPU" type="Button" parent="CenterContainer/VBoxContainer/PanelContainer/VBoxContainer/HBoxContainer"]
custom_minimum_size = Vector2(0, 54)
layout_mode = 2
size_flags_horizontal = 3
text = "Create (CPU)"
[node name="PanelContainer2" type="PanelContainer" parent="CenterContainer/VBoxContainer"]
custom_minimum_size = Vector2(1034, 0)
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/VBoxContainer/PanelContainer2"]
layout_mode = 2
[node name="GridContainer" type="GridContainer" parent="CenterContainer/VBoxContainer/PanelContainer2/VBoxContainer"]
layout_mode = 2
theme_override_constants/v_separation = 10
columns = 2
[node name="Label" type="Label" parent="CenterContainer/VBoxContainer/PanelContainer2/VBoxContainer/GridContainer"]
custom_minimum_size = Vector2(0, 30)
layout_mode = 2
text = "Raw Heightmap"
horizontal_alignment = 1
vertical_alignment = 2
[node name="Label2" type="Label" parent="CenterContainer/VBoxContainer/PanelContainer2/VBoxContainer/GridContainer"]
custom_minimum_size = Vector2(0, 30)
layout_mode = 2
text = "Computed Island"
horizontal_alignment = 1
vertical_alignment = 2
[node name="RawHeightmap" type="TextureRect" parent="CenterContainer/VBoxContainer/PanelContainer2/VBoxContainer/GridContainer"]
custom_minimum_size = Vector2(384, 384)
layout_mode = 2
size_flags_horizontal = 3
ignore_texture_size = true
stretch_mode = 5
[node name="ComputedHeightmap" type="TextureRect" parent="CenterContainer/VBoxContainer/PanelContainer2/VBoxContainer/GridContainer"]
custom_minimum_size = Vector2(384, 384)
layout_mode = 2
size_flags_horizontal = 3
ignore_texture_size = true
stretch_mode = 5
[node name="HBoxContainer" type="HBoxContainer" parent="CenterContainer/VBoxContainer/PanelContainer2/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="Label" type="Label" parent="CenterContainer/VBoxContainer/PanelContainer2/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Time elapsed: "
horizontal_alignment = 2
vertical_alignment = 1
[node name="Label2" type="Label" parent="CenterContainer/VBoxContainer/PanelContainer2/VBoxContainer/HBoxContainer"]
custom_minimum_size = Vector2(0, 40)
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(1, 0.941176, 0.47451, 1)
theme_override_font_sizes/font_size = 24
text = "... ms"
[connection signal="pressed" from="CenterContainer/VBoxContainer/PanelContainer/VBoxContainer/GridContainer/RandomButton" to="." method="_on_random_button_pressed"]
[connection signal="pressed" from="CenterContainer/VBoxContainer/PanelContainer/VBoxContainer/HBoxContainer/CreateButtonGPU" to="." method="_on_create_button_gpu_pressed"]
[connection signal="pressed" from="CenterContainer/VBoxContainer/PanelContainer/VBoxContainer/HBoxContainer/CreateButtonCPU" to="." method="_on_create_button_cpu_pressed"]

View File

@@ -0,0 +1,26 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="Compute Shader Heightmap"
run/main_scene="res://main.tscn"
config/features=PackedStringArray("4.0")
run/low_processor_mode=true
config/icon="res://icon.svg"
[display]
window/stretch/mode="canvas_items"
window/stretch/aspect="expand"
[rendering]
renderer/rendering_method="mobile"

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB