-
Notifications
You must be signed in to change notification settings - Fork 5
[WIP] add imgui controls for define macros #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Vipitis
wants to merge
26
commits into
main
Choose a base branch
from
imgui
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
a75d4df
add example
Vipitis 05da161
first sketch
Vipitis 4e0db7e
basic type inference
Vipitis 0410721
move UniformArray class
Vipitis 7603bf0
force unicode encoding for test
Vipitis 271a7b1
create constant buffer
Vipitis 0e7eafd
bind constant buffer
Vipitis 9198452
remove existing constants
Vipitis 50f51e9
working constant read
Vipitis 23a0f0f
working overlay
Vipitis 899981f
bind events
Vipitis 855a58e
fix limits
Vipitis c19eb27
add cli arg
Vipitis 223d0da
fix resize
Vipitis e5be542
reject needed macros
Vipitis e499fed
update example
Vipitis 5ca77ff
add dataclass
Vipitis 570667b
fix f string
Vipitis 371a2d9
allow negative values
Vipitis 847f661
enable multipass constants
Vipitis 1db492e
fixes when constants
Vipitis a965642
add Buffer Image preview
Vipitis cad5bb9
block mouse events
Vipitis 67c7630
enable slider reset
Vipitis 48f7509
support common pass constants
Vipitis 0418d25
make sliders infinite
Vipitis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| from wgpu_shadertoy import Shadertoy | ||
|
|
||
| # shadertoy source: https://www.shadertoy.com/view/Wf3SWn by Xor CC-BY-NC-SA 3.0 | ||
| # modified in Line73 to disassemble the for loop due to: https://github.com/gfx-rs/wgpu/issues/6208 | ||
|
|
||
| shader_code = """//glsl | ||
| /* | ||
| "Sunset" by @XorDev | ||
|
|
||
| Expanded and clarified version of my Sunset shader: | ||
| https://www.shadertoy.com/view/wXjSRt | ||
|
|
||
| Based on my tweet shader: | ||
| https://x.com/XorDev/status/1918764164153049480 | ||
| */ | ||
|
|
||
| //Output image brightness | ||
| #define BRIGHTNESS 1.0 | ||
|
|
||
| //Base brightness (higher = brighter, less saturated) | ||
| #define COLOR_BASE 1.5 | ||
| //Color cycle speed (radians per second) | ||
| #define COLOR_SPEED 0.5 | ||
| //RGB color phase shift (in radians) | ||
| #define RGB vec3(0.0, 1.0, 2.0) | ||
| //Color translucency strength | ||
| #define COLOR_WAVE 14.0 | ||
| //Color direction and (magnitude = frequency) | ||
| #define COLOR_DOT vec3(1,-1,0) | ||
|
|
||
| //Wave iterations (higher = slower) | ||
| #define WAVE_STEPS 8.0 | ||
| //Starting frequency | ||
| #define WAVE_FREQ 5.0 | ||
| //Wave amplitude | ||
| #define WAVE_AMP 0.6 | ||
| //Scaling exponent factor | ||
| #define WAVE_EXP 1.8 | ||
| //Movement direction | ||
| #define WAVE_VELOCITY vec3(0.2) | ||
|
|
||
|
|
||
| //Cloud thickness (lower = denser) | ||
| #define PASSTHROUGH 0.2 | ||
|
|
||
| //Cloud softness | ||
| #define SOFTNESS 0.005 | ||
| //Raymarch step | ||
| #define STEPS 100.0 | ||
| //Sky brightness factor (finicky) | ||
| #define SKY 10.0 | ||
| //Camera fov ratio (tan(fov_y/2)) | ||
| #define FOV 1.0 | ||
|
|
||
| void mainImage(out vec4 fragColor, in vec2 fragCoord) | ||
| { | ||
| //Raymarch depth | ||
| float z = 0.0; | ||
|
|
||
| //Step distance | ||
| float d = 0.0; | ||
| //Signed distance | ||
| float s = 0.0; | ||
|
|
||
| //Ray direction | ||
| vec3 dir = normalize( vec3(2.0*fragCoord - iResolution.xy, - FOV * iResolution.y)); | ||
|
|
||
| //Output color | ||
| vec3 col = vec3(0); | ||
|
|
||
| //Clear fragcolor and raymarch with 100 iterations | ||
| for(float i = 0.0; i<STEPS; i++) | ||
| { | ||
| //Compute raymarch sample point | ||
| vec3 p = z * dir; | ||
|
|
||
| //Turbulence loop | ||
| //https://www.shadertoy.com/view/3XXSWS | ||
| float j, f = WAVE_FREQ; | ||
| for(j = 0.0; j<WAVE_STEPS; j++) { | ||
| p += WAVE_AMP*sin(p*f - WAVE_VELOCITY*iTime).yzx / f; | ||
| f *= WAVE_EXP; | ||
| } | ||
| //Compute distance to top and bottom planes | ||
| s = 0.3 - abs(p.y); | ||
| //Soften and scale inside the clouds | ||
| d = SOFTNESS + max(s, -s*PASSTHROUGH) / 4.0; | ||
| //Step forward | ||
| z += d; | ||
| //Coloring with signed distance, position and cycle time | ||
| float phase = COLOR_WAVE * s + dot(p,COLOR_DOT) + COLOR_SPEED*iTime; | ||
| //Apply RGB phase shifts, add base brightness and correct for sky | ||
| col += (cos(phase - RGB) + COLOR_BASE) * exp(s*SKY) / d; | ||
| } | ||
| //Tanh tonemapping | ||
| //https://www.shadertoy.com/view/ms3BD7 | ||
| col *= SOFTNESS / STEPS * BRIGHTNESS; | ||
| fragColor = vec4(tanh(col * col), 1.0); | ||
| } | ||
| """ | ||
|
|
||
| shader = Shadertoy(shader_code, resolution=(800, 450), imgui=True) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| shader.show() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,227 @@ | ||
| import re | ||
| from imgui_bundle import imgui as ig | ||
|
|
||
| from .utils import UniformArray | ||
| from wgpu.utils.imgui import ImguiWgpuBackend | ||
| # from wgpu_shadertoy.passes import RenderPass #circular import-.- | ||
| from dataclasses import dataclass | ||
| from math import log | ||
|
Check failure on line 8 in wgpu_shadertoy/imgui.py
|
||
|
|
||
|
|
||
| # could imgui become just another RenderPass after Image? I got to understand backend vs renderer first. | ||
| # make become part of .passes?? | ||
| # todo: raise error if imgui isn't installed (only if this module is required?) | ||
|
|
||
|
|
||
| @dataclass | ||
| class ShaderConstant: | ||
| # renderpass_pass: str #maybe this is a RenderPass pointer? likely redundant | ||
| line_number: int | ||
| original_line: str | ||
| name: str | ||
| value: int | float | ||
| shader_dtype: str # float, int, vec2, vec3, bool etc. | ||
|
|
||
| def c_type_format(self) -> str: | ||
| # based on these for the memoryview cast: | ||
| # https://docs.python.org/3/library/struct.html#format-characters | ||
| if self.shader_dtype == "float": | ||
| return "f" | ||
| elif self.shader_dtype == "int": | ||
| return "i" | ||
| elif self.shader_dtype == "uint": | ||
| return "I" | ||
| # add more types as needed | ||
| return "?" | ||
|
|
||
| def parse_constants(code:str) -> list[ShaderConstant]: | ||
| # todo: | ||
| # WGSL variants?? | ||
| # re/tree-sitter/loops and functions? | ||
| # parse and collect constants from shadercode (including common pass?) | ||
| # get information about the line, the type and it's initial value | ||
| # make up a range (maybe just the order of magnitude + 1 as max and 0 as min (what about negative values?)) | ||
| # what is the return type? (line(int), type(str), value(float/tuple/int?)) maybe proper dataclasss for once | ||
|
|
||
| # for multipass shader this might need to be per pass (rpass.value) ? | ||
| # mataches the macro: #define NAME VALUE | ||
| # TODO there can be characters in numerical literals, such as x and o for hex and octal representation or e for scientific notation | ||
| # technically the macros can also be an expression that is evaluated to be a number... such as # define DOF 10..0/30.0 - so how do we deal with that? | ||
| define_pattern = re.compile(r"#\s*define\s+(\w+)\s+(-?[\d.]+)") #for numerical literals right now. | ||
| if_def_template = r"#(el)?if\s+" #preprocessor ifdef blocks can't become uniforms. replacing these dynamically will be difficult. | ||
|
|
||
| constants = [] | ||
| for li, line in enumerate(code.splitlines()): | ||
| match = define_pattern.match(line.strip()) | ||
| if match: | ||
| name, value = match.groups() | ||
| if_def_pattern = re.compile(if_def_template + name) | ||
| if if_def_pattern.findall(code): | ||
| #.findall over .match because because not only the beginning matters here | ||
| print(f"skipping constant {name}, it needs to stay a macro") | ||
| continue | ||
|
|
||
| if "." in value: #value.isdecimal? | ||
| # TODO: wgsl needs to be more specific (f32 for example?) - but there is no preprocessor anyways... | ||
| dtype = "float" #default float (32bit) | ||
| value = float(value) | ||
| elif value.isdecimal(): # value.isnumeric? | ||
| dtype = "int" # "big I (32bit)" | ||
| value = int(value) | ||
| else: | ||
| # TODO complexer types? | ||
| print(f"can't parse type for constant {name} with value {value}, skipping") | ||
| continue | ||
|
|
||
| constant = ShaderConstant( | ||
| # renderpass_pass="image", # TODO: shouldn't be names. | ||
| line_number=li, | ||
| original_line=line.strip(), | ||
| name=name, | ||
| value=value, | ||
| shader_dtype=dtype | ||
| ) | ||
| # todo: remove lines here? (comment out better) | ||
| constants.append(constant) | ||
| print(f"In line {li} found constant: {name} with value: {value} of dtype {dtype}") # maybe name renderpass too? | ||
|
|
||
| # maybe just named tuple instead of dataclass? | ||
| return constants | ||
|
|
||
| def make_uniform(constants) -> UniformArray: | ||
| arr_data = [] | ||
| for constant in constants: | ||
| arr_data.append(tuple([constant.name, constant.c_type_format(), 1])) | ||
| data = UniformArray(*arr_data) | ||
|
|
||
| # init data | ||
| for constant in constants: | ||
| data[constant.name] = constant.value | ||
|
|
||
| # TODO: | ||
| # is there issues with padding? (maybe solve in the class) | ||
| # figure out order due to padding/alignment: https://www.w3.org/TR/WGSL/#alignment-and-size | ||
| # return a UniformArray object too (cycling import?) also needs device handed down. | ||
| # (does this need to be a class to update the values?) | ||
| return data | ||
|
|
||
| # TODO mark private? | ||
| def construct_imports(constants: list[ShaderConstant], constant_binding_idx: int) -> str: | ||
| # codegen the import block for this uniform (including binding? - which number?) | ||
| # could be part of the UniformArray class maybe? | ||
| # to be pasted near the top of the fragment shader code. | ||
| # alternatively: insert these in the ShadertoyInputs uniform? | ||
| # better yet: use push constants | ||
| # TODO: can you even import a uniform struct and then have these available as global? | ||
| # maybe I got to add them back in as #define name = constant.name or something | ||
|
|
||
| if not constants: | ||
| return "" | ||
|
|
||
| var_init_lines = [] | ||
| var_mapping_lines = [] | ||
| for const in constants: | ||
| var_init_lines.append(f"{const.shader_dtype} {const.name};") | ||
| var_mapping_lines.append(f"# define {const.name} const_input{constant_binding_idx}.{const.name}") | ||
|
|
||
| new_line = "\n" # pytest was complaining about having blackslash in an f-string | ||
| code_construct = f""" | ||
| uniform struct ConstantInput{constant_binding_idx} {{ | ||
| {new_line.join(var_init_lines)} | ||
| }}; | ||
| layout(binding = {constant_binding_idx}) uniform ConstantInput{constant_binding_idx} const_input{constant_binding_idx}; | ||
| {new_line.join(var_mapping_lines)} | ||
| """ | ||
| # the identifier name includes the digit so common doesn't cause redefinition! | ||
| # TODO messed up indentation... textwrap.dedent? | ||
| return code_construct | ||
|
|
||
| def replace_constants(code: str, constants: list[ShaderConstant], constant_binding_idx: int) -> str: | ||
| """ | ||
| comment out existing constants and redefine them with uniform struct | ||
| """ | ||
| code_lines = code.splitlines() | ||
| for const in constants: | ||
| # comment out existing constants | ||
| code_lines[const.line_number] = f"// {code_lines[const.line_number]}" | ||
|
|
||
| constant_headers = construct_imports(constants, constant_binding_idx) | ||
| code_lines.insert(0, constant_headers) | ||
|
|
||
| return "\n".join(code_lines) | ||
|
|
||
|
|
||
| # imgui stuff | ||
| def update_gui(): | ||
| # todo: look at exmaples nad largely copy nad paste, will be called in the draw_frame function I think. | ||
|
|
||
| pass | ||
|
|
||
|
|
||
| def gui(renderpasses: list["RenderPass"]): | ||
| ig.new_frame() | ||
| ig.set_next_window_pos((0, 0), ig.Cond_.appearing) | ||
| ig.set_next_window_size((0, 0), ig.Cond_.appearing) # auto size not wide enough with text :/ | ||
| ig.begin("Shader constants", None) | ||
| ig.text('in-dev imgui overlay\n') | ||
|
|
||
| if ig.is_item_hovered(): | ||
| ig.set_tooltip("TODO") | ||
|
|
||
| # maybe we should have a global main or utils.get_main()? | ||
| main = renderpasses[0].main | ||
|
|
||
| # TODO: avoid duplication, maybe common should be a renderpass instance (at least a little bit) - or we iterate through constants lists | ||
| if main._common_constants: | ||
| if ig.collapsing_header("Common Constants", flags=ig.TreeNodeFlags_.default_open): | ||
| for const in main._common_constants: | ||
| if const.shader_dtype == "float": | ||
| _, main._common_constants_data[const.name] = ig.drag_float(f"{const.name}", main._common_constants_data[const.name], v_speed=abs(const.value)*0.01) | ||
| elif const.shader_dtype == "int": | ||
| _, main._common_constants_data[const.name] = ig.drag_int(f"{const.name}", main._common_constants_data[const.name], v_speed=abs(const.value)*0.01) | ||
| if ig.is_item_hovered() and ig.is_mouse_clicked(ig.MouseButton_.right): | ||
| main._common_constants_data[const.name] = const.value | ||
|
|
||
| for rp in renderpasses: # TODO: most likely add common here? | ||
| constants = rp._constants | ||
| constants_data = rp._constants_data | ||
| if ig.collapsing_header(f"{rp} Constants", flags=ig.TreeNodeFlags_.default_open): | ||
| if hasattr(rp, "texture_front"): # isinstance(rp, BufferRenderPass) | ||
| # make this another toggle? or a whole 2nd UI? | ||
| front_view = rp.texture_front.create_view() | ||
| front_ref = rp.main._imgui_backend.register_texture(front_view) | ||
| scale = 0.25 # TODO dynamic zoom via width? | ||
| # TODO: can we force a background? do we need to request additional view formats? -> ig.image_with_bg? | ||
| buf_img = ig.image(front_ref, (front_view.size[0]*scale, front_view.size[1]*scale), uv0=(0,1), uv1=(1,0)) | ||
|
|
||
| # create the sliders? -> drag widget! | ||
| for const in constants: | ||
| if const.shader_dtype == "float": | ||
| _, constants_data[const.name] = ig.drag_float(f"{const.name}", constants_data[const.name], v_speed=abs(const.value)*0.01) | ||
| elif const.shader_dtype == "int": | ||
| _, constants_data[const.name] = ig.drag_int(f"{const.name}", constants_data[const.name], v_speed=abs(const.value)*0.01) | ||
| if ig.is_item_hovered() and ig.is_mouse_clicked(ig.MouseButton_.right): | ||
| constants_data[const.name] = const.value | ||
|
|
||
| ig.text("Right sliders/drag to reset") | ||
|
|
||
| # TODO: control the size of these headers to make the window as small as possible after they are collapsed! | ||
| ig.end() | ||
| ig.end_frame() | ||
| ig.render() | ||
| return ig.get_draw_data() | ||
|
|
||
| def get_backend(device, canvas, render_texture_format): | ||
| """ | ||
| copied from backend example, held here to avoid clutter in the main class | ||
| """ | ||
|
|
||
| # init imgui backend | ||
| ig.create_context() | ||
| imgui_backend = ImguiWgpuBackend(device, render_texture_format) | ||
| imgui_backend.io.display_size = canvas.get_logical_size() | ||
| imgui_backend.io.display_framebuffer_scale = ( | ||
| canvas.get_pixel_ratio(), | ||
| canvas.get_pixel_ratio(), | ||
| ) | ||
| return imgui_backend | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, came cross here from pygfx/wgpu-py#729 (comment).
It’s generally better not to create and register new texture views every frame in the render loop. Instead, a global texture view should be registered and used during initialization, or a imgui.TextureRef can be registered and created only at the first time the GUI is rendered.
If it’s absolutely necessary to create and register new texture views each frame in the render loop, you must manually call the newly added
unregistermethod in pygfx/wgpu-py#749 to release the old texture view resources.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cheers for the heads up,
the render target for the buffer passes is switching back and forth between two textures(maybe I should also keep both views...) so I will likely have to register both of them during initialization and figure out a way to select the correct one in the gui function or swap them around after the draw function too. Sorta is a little against the "intermediate mode" concept.
I will have a think since this whole branch needs a better pattern anyway, why too many
if self._imgui