mirror of
https://github.com/godotengine/godot-demo-projects.git
synced 2025-12-16 13:30:07 +01:00
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:
34
misc/compute_shader_heightmap/README.md
Normal file
34
misc/compute_shader_heightmap/README.md
Normal 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
|
||||
|
||||

|
||||
61
misc/compute_shader_heightmap/compute_shader.glsl
Normal file
61
misc/compute_shader_heightmap/compute_shader.glsl
Normal 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);
|
||||
}
|
||||
14
misc/compute_shader_heightmap/compute_shader.glsl.import
Normal file
14
misc/compute_shader_heightmap/compute_shader.glsl.import
Normal 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]
|
||||
|
||||
1
misc/compute_shader_heightmap/icon.svg
Normal file
1
misc/compute_shader_heightmap/icon.svg
Normal 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 |
37
misc/compute_shader_heightmap/icon.svg.import
Normal file
37
misc/compute_shader_heightmap/icon.svg.import
Normal 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
|
||||
260
misc/compute_shader_heightmap/main.gd
Normal file
260
misc/compute_shader_heightmap/main.gd
Normal 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)
|
||||
158
misc/compute_shader_heightmap/main.tscn
Normal file
158
misc/compute_shader_heightmap/main.tscn
Normal 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"]
|
||||
26
misc/compute_shader_heightmap/project.godot
Normal file
26
misc/compute_shader_heightmap/project.godot
Normal 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"
|
||||
0
misc/compute_shader_heightmap/screenshots/.gdignore
Normal file
0
misc/compute_shader_heightmap/screenshots/.gdignore
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
Reference in New Issue
Block a user