Faster Preview View (#15884)

Co-authored-by: Kris Austin <kris.austin@gmail.com>
This commit is contained in:
Ian Bassi
2026-10-02 14:45:58 -03:00
committed by GitHub
co-authored by Kris Austin
parent 4ffba13210
commit c79af20378
37 changed files with 1740 additions and 290 deletions
+93
View File
@@ -0,0 +1,93 @@
# 3D Scene Benchmark: High Level Design
## Why it exists
Rendering changes, such as the realistic view, shadows or SSAO, need a number to compare
before and after, and user reports of a slow viewport need a way to say how slow. The FPS
overlay and the render timings overlay show live values while someone drags the camera,
which varies from run to run with the model, the path of the mouse and the view.
The benchmark renders a fixed model along a fixed camera path in both 3D views, so two
runs on the same machine differ only by the code or the settings, and prints a report that
can be pasted into an issue.
## What it does
`run_scene_benchmark()` in `src/slic3r/GUI/SceneBenchmark.cpp` is reached from Help >
Benchmark 3D Scene, the command palette and Preferences > Graphics. After a confirmation
it starts a new project, which asks to save the current one if needed, loads the
OrcaSliced Combo handy model and arranges it. A small dialog in a corner of the 3D view
then shows the progress; every other window is disabled until the run ends, so a click
cannot change the scene being measured. Cancel or Esc stops the run.
The run goes through these stages, driven by a timer while it waits and by idle events
while it renders:
1. Loading: waits until the UI job worker is idle, so the arrange job has moved the
objects. The orbit target is the center of the objects on the current plate, and the
base zoom fits their bounding box in the viewport.
2. Prepare: renders the scene in the Prepare view.
3. Slicing: slices the plate and switches to Preview, then waits for the G-code preview
to load. If slicing fails, the report holds Prepare alone.
4. Preview: renders the scene in the Preview view, with the slicing progress notification
hidden.
5. Layers: renders the Preview view again while the layer slider moves, which is what
makes dragging it feel slow on large prints.
The dialog then shows the report, with a button to copy it. A scene cut short, because its
view was hidden, is left out of the report.
## Rendering a scene
Each scene renders 30 warm-up frames, then the camera path twice, 360 frames each time.
- The first pass times the frames. A frame's time is the interval between the starts of
consecutive benchmark frames, so it includes the event loop between them.
- The second pass averages the render timings. The frame profiler flushes the GL command
queue after each section, which slows a frame down, so it only runs in this pass.
`FrameProfiler::start_averaging()` flags every frame begun afterwards, and
`finish_averaging()` waits for the flagged frames still on the GPU and returns the mean
CPU and GPU time of each section per profiled frame.
- A section's GPU time is taken between a timestamp before its commands and one after
them. The first is only sent along with those commands, so when the GPU finishes a
section before the CPU has issued the next one, the wait counts in neither.
The dialog renders one frame per idle event by calling `GLCanvas3D::render()`, which
redraws the whole scene. While `GLCanvas3D::set_benchmarking()` is on, the canvas does not
render from its own idle handler, so no other frame is drawn in between, and it skips the
picking pass and the FPS and render timings overlays, which depend on the mouse and on
preferences. The FPS cap does not apply, since it only paces idle redraws.
VSync is turned off for the scene through `wxGLCanvas::SetSwapInterval(0)`, so the frame
rate is what the GPU and CPU can reach rather than the display's refresh rate, and the
previous interval is restored afterwards. When the platform cannot report the current
interval (EGL), it is left as it is and the report says so.
The camera path makes two turns around the target while the view rises three times from
25 degrees below the plate to 85 degrees above it and the zoom goes twice between 0.6 and
1.4 times the base zoom. The camera stays at the default distance, so the perspective is
the same in every run. The camera the scene started with is restored at its end.
The Layers scene holds the camera at the start of that path and moves the top of the layer
slider instead, from the last layer down to the first and back up in each pass. It goes
through `IMSlider::SetHigherValue()`, as a drag does, so every frame applies a new layer
range to the toolpaths and the objects before drawing them, including a new shadow map when
the shadows are static. Its warm-up frames lead into the start of the path, so the slider
moves in every frame. The slider position it started from is restored at its end.
## The report
The report is plain English text, so it reads the same in every language:
- The version and build commit, the GPU and OpenGL version, the viewport size and camera
type, and the graphics settings that change the cost of a frame: MSAA samples as read
from the framebuffer, FXAA, the scene cache, VSync and the realistic view options.
- The printer and process presets the model was sliced with, marked when they have
unsaved changes, and the toolpath vertices and layers they produced, since the Preview
scenes cost more with more toolpaths.
- For each scene, the average FPS and the average, median, 95th percentile, 99th
percentile and maximum frame time. Percentiles are nearest-rank, so each is a measured
frame (`frame_time_stats()`).
- For each scene, the render timings table: the CPU and GPU milliseconds of each section
of a frame, and their total. Without timer queries (OpenGL 3.3 or `ARB_timer_query`) the
table says that the driver does not support them.
+4 -2
View File
@@ -65,8 +65,10 @@ closes the open gizmo, so neither tab ever shows a stale cut.
as the plane faces; otherwise it is discarded. The cut face keeps the depth of the fragment it
replaces, which is safe: along that ray everything else still shown lies behind the plane. The
shader writes no `gl_FragDepth`, so early depth testing survives. Option markers are cut away
whole, by their centres. The shadow casters share the segment shader, so what is cut away casts
no shadow either. Preview shells are drawn by another shader and are not clipped.
whole, by their centres. The shadow casters draw with a program of their own, which takes the
plane and discards the fragments on the clipped side, so what is cut away casts no shadow either.
Their cut faces are not drawn, since the part left behind casts the shadow of its own section.
Preview shells are drawn by another shader and are not clipped.
- **Picking.** `get_raycaster_clipping_plane()` returns the same plane, so hover, selection and
the perspective pan anchor ignore what the user cannot see.
+1
View File
@@ -180,6 +180,7 @@ src/slic3r/GUI/PrivacyUpdateDialog.cpp
src/slic3r/GUI/PublishDialog.cpp
src/slic3r/GUI/PublishSettingsDialog.cpp
src/slic3r/GUI/SavePresetDialog.cpp
src/slic3r/GUI/SceneBenchmark.cpp
src/slic3r/GUI/Search.cpp
src/slic3r/GUI/SettingsIndex.cpp
src/slic3r/GUI/SpeedDialDialog.cpp
+5 -1
View File
@@ -63,6 +63,10 @@ uniform float shadow_map_texel;
// LIGHT_TOP_DIR in eye space (matches the diffuse light used for shading in gouraud.vs).
const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
// ORCA: realistic view - static shadows also light the scene from their fixed light.
uniform bool use_static_light;
uniform vec3 static_light_dir;
vec3 top_light_dir() { return use_static_light ? static_light_dir : SHADOW_LIGHT_DIR; }
varying vec3 clipping_planes_dots;
varying float color_clip_plane_dot;
@@ -155,7 +159,7 @@ float shadow_shade()
// Slope-scaled depth bias: larger where the surface grazes / faces away from the light. This
// suppresses self-shadow acne without discarding real shadows cast by other objects onto
// back-facing surfaces (e.g. the shaded back/tip of a cone sitting inside a larger shadow).
float NdotL = dot(normalize(eye_normal), SHADOW_LIGHT_DIR);
float NdotL = dot(normalize(eye_normal), top_light_dir());
float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));
// 5x5 PCF: softens shadow edges into a smooth penumbra and blurs residual facet acne.
float sum = 0.0;
+6 -2
View File
@@ -39,6 +39,10 @@ uniform vec2 z_range;
uniform vec4 clipping_plane;
// Color clip plane - general orientation. Used by the cut gizmo.
uniform vec4 color_clip_plane;
// ORCA: realistic view - static shadows also light the scene from their fixed light.
uniform bool use_static_light;
uniform vec3 static_light_dir;
vec3 top_light_dir() { return use_static_light ? static_light_dir : LIGHT_TOP_DIR; }
attribute vec3 v_position;
attribute vec3 v_normal;
@@ -60,11 +64,11 @@ void main()
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
float NdotL = max(dot(eye_normal, top_light_dir()), 0.0);
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
vec4 position = view_model_matrix * vec4(v_position, 1.0);
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-top_light_dir(), eye_normal)), 0.0), LIGHT_TOP_SHININESS);
// Perform the same lighting calculation for the 2nd light source (no specular applied).
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
+7 -3
View File
@@ -70,6 +70,10 @@ uniform sampler2D shadow_map;
uniform mat4 shadow_light_vp;
uniform float shadow_intensity;
uniform float shadow_map_texel;
// ORCA: realistic view - static shadows also light the scene from their fixed light.
uniform bool use_static_light;
uniform vec3 static_light_dir;
vec3 top_light_dir() { return use_static_light ? static_light_dir : LIGHT_TOP_DIR; }
varying vec3 clipping_planes_dots;
varying float color_clip_plane_dot;
@@ -193,7 +197,7 @@ float shadow_shade()
// Slope-scaled depth bias: larger where the surface grazes / faces away from the light. This
// suppresses self-shadow acne without discarding real shadows cast by other objects onto
// back-facing surfaces (e.g. the shaded back/tip of a cone sitting inside a larger shadow).
float NdotL = dot(normalize(eye_normal), LIGHT_TOP_DIR);
float NdotL = dot(normalize(eye_normal), top_light_dir());
float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));
// 5x5 PCF: softens shadow edges into a smooth penumbra and blurs residual facet acne.
float sum = 0.0;
@@ -248,9 +252,9 @@ void main()
vec3 normal = normalize(eye_normal);
vec3 view_dir = normalize(-eye_position);
float NdotL_top = max(dot(normal, LIGHT_TOP_DIR), 0.0);
float NdotL_top = max(dot(normal, top_light_dir()), 0.0);
float diffuse = INTENSITY_AMBIENT + NdotL_top * LIGHT_TOP_DIFFUSE;
vec3 half_top = normalize(LIGHT_TOP_DIR + view_dir);
vec3 half_top = normalize(top_light_dir() + view_dir);
float specular = LIGHT_TOP_SPECULAR * pow(max(dot(normal, half_top), 0.0), LIGHT_TOP_SHININESS);
float NdotL_front = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+4
View File
@@ -6,6 +6,8 @@ uniform sampler2D shadow_map;
uniform mat4 shadow_light_vp;
uniform float shadow_intensity;
uniform float shadow_map_texel;
// Plate area a shadow can reach, min xy then max xy; the rest is skipped before any lookup.
uniform vec4 shadow_bounds;
varying vec4 world_pos;
@@ -33,6 +35,8 @@ float shadow_occlusion()
void main()
{
if (any(lessThan(world_pos.xy, shadow_bounds.xy)) || any(greaterThan(world_pos.xy, shadow_bounds.zw)))
discard;
float occ = shadow_occlusion();
if (occ <= 0.0)
discard;
+5 -1
View File
@@ -72,6 +72,10 @@ uniform float shadow_map_texel;
// LIGHT_TOP_DIR in eye space (matches the diffuse light used for shading in gouraud.vs).
const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
// ORCA: realistic view - static shadows also light the scene from their fixed light.
uniform bool use_static_light;
uniform vec3 static_light_dir;
vec3 top_light_dir() { return use_static_light ? static_light_dir : SHADOW_LIGHT_DIR; }
in vec3 clipping_planes_dots;
in float color_clip_plane_dot;
@@ -206,7 +210,7 @@ float shadow_shade()
// Slope-scaled depth bias: larger where the surface grazes / faces away from the light. This
// suppresses self-shadow acne without discarding real shadows cast by other objects onto
// back-facing surfaces (e.g. the shaded back/tip of a cone sitting inside a larger shadow).
float NdotL = dot(normalize(eye_normal), SHADOW_LIGHT_DIR);
float NdotL = dot(normalize(eye_normal), top_light_dir());
float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));
// 5x5 PCF: softens shadow edges into a smooth penumbra and blurs residual facet acne.
float sum = 0.0;
+6 -2
View File
@@ -39,6 +39,10 @@ uniform vec2 z_range;
uniform vec4 clipping_plane;
// Color clip plane - general orientation. Used by the cut gizmo.
uniform vec4 color_clip_plane;
// ORCA: realistic view - static shadows also light the scene from their fixed light.
uniform bool use_static_light;
uniform vec3 static_light_dir;
vec3 top_light_dir() { return use_static_light ? static_light_dir : LIGHT_TOP_DIR; }
in vec3 v_position;
in vec3 v_normal;
@@ -60,11 +64,11 @@ void main()
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
float NdotL = max(dot(eye_normal, top_light_dir()), 0.0);
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
vec4 position = view_model_matrix * vec4(v_position, 1.0);
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-top_light_dir(), eye_normal)), 0.0), LIGHT_TOP_SHININESS);
// Perform the same lighting calculation for the 2nd light source (no specular applied).
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
+7 -3
View File
@@ -80,6 +80,10 @@ uniform sampler2D shadow_map;
uniform mat4 shadow_light_vp;
uniform float shadow_intensity;
uniform float shadow_map_texel;
// ORCA: realistic view - static shadows also light the scene from their fixed light.
uniform bool use_static_light;
uniform vec3 static_light_dir;
vec3 top_light_dir() { return use_static_light ? static_light_dir : LIGHT_TOP_DIR; }
in vec3 clipping_planes_dots;
in float color_clip_plane_dot;
@@ -250,7 +254,7 @@ float shadow_shade()
// Slope-scaled depth bias: larger where the surface grazes / faces away from the light. This
// suppresses self-shadow acne without discarding real shadows cast by other objects onto
// back-facing surfaces (e.g. the shaded back/tip of a cone sitting inside a larger shadow).
float NdotL = dot(normalize(eye_normal), LIGHT_TOP_DIR);
float NdotL = dot(normalize(eye_normal), top_light_dir());
float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));
// 5x5 PCF: softens shadow edges into a smooth penumbra and blurs residual facet acne.
float sum = 0.0;
@@ -305,9 +309,9 @@ void main()
vec3 normal = normalize(eye_normal);
vec3 view_dir = normalize(-eye_position);
float NdotL_top = max(dot(normal, LIGHT_TOP_DIR), 0.0);
float NdotL_top = max(dot(normal, top_light_dir()), 0.0);
float diffuse = INTENSITY_AMBIENT + NdotL_top * LIGHT_TOP_DIFFUSE;
vec3 half_top = normalize(LIGHT_TOP_DIR + view_dir);
vec3 half_top = normalize(top_light_dir() + view_dir);
float specular = LIGHT_TOP_SPECULAR * pow(max(dot(normal, half_top), 0.0), LIGHT_TOP_SHININESS);
float NdotL_front = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
+4
View File
@@ -6,6 +6,8 @@ uniform sampler2D shadow_map;
uniform mat4 shadow_light_vp;
uniform float shadow_intensity;
uniform float shadow_map_texel;
// Plate area a shadow can reach, min xy then max xy; the rest is skipped before any lookup.
uniform vec4 shadow_bounds;
in vec4 world_pos;
@@ -35,6 +37,8 @@ float shadow_occlusion()
void main()
{
if (any(lessThan(world_pos.xy, shadow_bounds.xy)) || any(greaterThan(world_pos.xy, shadow_bounds.zw)))
discard;
float occ = shadow_occlusion();
if (occ <= 0.0)
discard;
+6 -2
View File
@@ -304,6 +304,9 @@ void AppConfig::set_defaults()
if (get(SETTING_OPENGL_SHOW_FPS_OVERLAY).empty())
set_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY, false);
if (get(SETTING_OPENGL_SHOW_RENDER_TIMINGS).empty())
set_bool(SETTING_OPENGL_SHOW_RENDER_TIMINGS, false);
if (get(SETTING_OPENGL_REALISTIC_MODE).empty())
set_bool(SETTING_OPENGL_REALISTIC_MODE, false);
@@ -316,8 +319,9 @@ void AppConfig::set_defaults()
if (get(SETTING_OPENGL_SHADING_MODEL).empty())
set(SETTING_OPENGL_SHADING_MODEL, "gouraud");
if (get(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS).empty())
set_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS, false);
// Replaces the on/off setting, whose shadows turned with the camera.
if (get(SETTING_OPENGL_REALISTIC_SHADOWS).empty())
set(SETTING_OPENGL_REALISTIC_SHADOWS, get_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS) ? "orbit" : "off");
if (get(SETTING_OPENGL_PHONG_SMOOTH_NORMALS).empty())
set_bool(SETTING_OPENGL_PHONG_SMOOTH_NORMALS, false);
+3
View File
@@ -37,10 +37,13 @@ using namespace nlohmann;
#define SETTING_OPENGL_SCENE_CACHE "opengl_scene_cache"
#define SETTING_OPENGL_SKIP_IDENTICAL_FRAMES "opengl_skip_identical_frames"
#define SETTING_OPENGL_SHOW_FPS_OVERLAY "opengl_show_fps_overlay"
#define SETTING_OPENGL_SHOW_RENDER_TIMINGS "opengl_show_render_timings"
#define SETTING_OPENGL_REALISTIC_MODE "opengl_realistic_mode"
#define SETTING_OPENGL_REALISTIC_PHONG "opengl_realistic_phong"
#define SETTING_OPENGL_SHADING_MODEL "opengl_shading_model"
#define SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS "opengl_phong_basic_plate_shadows"
// off, static (light fixed in the world) or orbit (light turning with the camera)
#define SETTING_OPENGL_REALISTIC_SHADOWS "opengl_realistic_shadows"
#define SETTING_OPENGL_PHONG_SSAO "opengl_phong_ssao"
#define SETTING_OPENGL_PHONG_SMOOTH_NORMALS "opengl_phong_smooth_normals"
#define SETTING_OPENGL_REALISTIC_PREVIEW "opengl_realistic_preview"
+2
View File
@@ -37,6 +37,8 @@ static constexpr float MAX_WIPES_RADIUS_MM = 1.0f;
// Used for positions, displacements and so on.
//
using Vec3 = std::array<float, 3>;
// ORCA: the per vertex data uploaded as GL_RGBA32F, see extract_pos_and_or_hwa().
using Vec4 = std::array<float, 4>;
//
// 4x4 square matrix with elements in column-major order:
+4
View File
@@ -80,6 +80,10 @@ public:
// ORCA: section view. Cuts away where dot(plane, (x, y, z, 1)) < 0; { 0, 0, 0, 1 } keeps all.
//
void set_clipping_plane(const std::array<float, 4>& plane);
//
// ORCA: direction to the top light in eye space, shading the toolpaths and biasing their shadow lookup.
//
void set_light_top_dir(const Vec3& direction);
//
// ************************************************************************
+86 -24
View File
@@ -14,7 +14,7 @@ static const char* Segments_Vertex_Shader =
"#version 150\n"
"#define POINTY_CAPS\n"
"#define FIX_TWISTING\n"
"const vec3 light_top_dir = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
"uniform vec3 light_top_dir;\n"
"const float light_top_diffuse = 0.6 * 0.8;\n"
// ORCA: the specular was 0.6 * 0.125, too faint to give the filament any sheen.
"const float light_top_specular = 0.6 * 0.25;\n"
@@ -31,13 +31,9 @@ static const char* Segments_Vertex_Shader =
"uniform samplerBuffer height_width_angle_tex;\n"
"uniform samplerBuffer color_tex;\n"
"uniform usamplerBuffer segment_index_tex;\n"
// ORCA: 0 during the shadow caster pass - the bias below shifts eye_position but not
// world_position, so the caster would write a depth the receiver never looks up.
"uniform float bias_scale;\n"
// draw the instances last to first, top layers before the ones they hide, so that early depth
// rejection discards most of the hidden fragments; set when the camera looks down on the print
"uniform int reverse_order;\n"
"uniform int instance_count;\n"
// ORCA: draw last segment first, so looking down the top layers hide the rest from the fragment shader.
"uniform bool reverse_order;\n"
"uniform int instances_count;\n"
// ORCA: section view
"uniform vec4 clipping_plane;\n"
"in int vertex_id;\n"
@@ -53,13 +49,22 @@ static const char* Segments_Vertex_Shader =
// ORCA: section view - the segment and the light on its cut face
"flat out int segment_id;\n"
"out vec3 cut_color_direct;\n"
"vec3 decode_color(float color) {\n"
// ORCA: the layers greyed or dimmed around the one the sliders show, see ViewerImpl::set_layer_colors().
"uniform ivec2 lit_layers;\n"
"uniform int grey_below_layer;\n"
"uniform float dim_brightness;\n"
"uniform int kept_vertex;\n"
"vec3 decode_color(float color, int id, int layer) {\n"
" int c = int(round(color));\n"
" int r = (c >> 16) & 0xFF;\n"
" int g = (c >> 8) & 0xFF;\n"
" int b = (c >> 0) & 0xFF;\n"
" vec3 rgb = vec3((c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF);\n"
" if (id != kept_vertex && layer != lit_layers.x && layer != lit_layers.y) {\n"
" if (dim_brightness >= 0.0)\n"
" rgb = floor(rgb * dim_brightness);\n"
" else if (layer < grey_below_layer)\n"
" rgb = vec3(64.0);\n"
" }\n"
" float f = 1.0 / 255.0f;\n"
" return f * vec3(r, g, b);\n"
" return f * rgb;\n"
"}\n"
"float direct_lighting(vec3 eye_position, vec3 eye_normal) {\n"
" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n"
@@ -68,7 +73,7 @@ static const char* Segments_Vertex_Shader =
" return top_diffuse + front_diffuse + top_specular;\n"
"}\n"
"void main() {\n"
" int instance = (reverse_order != 0) ? instance_count - 1 - gl_InstanceID : gl_InstanceID;\n"
" int instance = reverse_order ? instances_count - 1 - gl_InstanceID : gl_InstanceID;\n"
" int id_a = int(texelFetch(segment_index_tex, instance).r);\n"
" int id_b = id_a + 1;\n"
" vec3 pos_a = texelFetch(position_tex, id_a).xyz;\n"
@@ -157,9 +162,9 @@ static const char* Segments_Vertex_Shader =
" }\n"
" vec3 eye_position = (view_matrix * vec4(pos, 1.0)).xyz;\n"
" // ORCA: Apply bias to z-position to avoid z-fighting\n"
" eye_position.z += bias * bias_scale;\n"
" eye_position.z += bias;\n"
" vec3 eye_normal = (view_matrix * vec4(normalize(pos - endpoint_pos), 0.0)).xyz;\n"
" vec3 color_base = decode_color(texelFetch(color_tex, id).r);\n"
" vec3 color_base = decode_color(texelFetch(color_tex, id).r, id, int(texelFetch(position_tex, id).w));\n"
" color = color_base * (ambient + emission);\n"
" color_direct = color_base * direct_lighting(eye_position, eye_normal);\n"
" world_position = pos;\n"
@@ -170,12 +175,60 @@ static const char* Segments_Vertex_Shader =
" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n"
"}\n";
// ORCA: realistic view - shadow caster, one light facing ribbon per segment, not instanced as tiny instances are slow.
static const char* Segments_Shadow_Caster_Vertex_Shader =
"#version 150\n"
"const vec3 UP = vec3(0, 0, 1);\n"
"const int CORNERS[6] = int[](0, 1, 2, 0, 2, 3);\n"
"uniform mat4 view_matrix;\n"
"uniform mat4 projection_matrix;\n"
"uniform samplerBuffer position_tex;\n"
"uniform samplerBuffer height_width_angle_tex;\n"
"uniform usamplerBuffer segment_index_tex;\n"
"uniform bool reverse_order;\n"
"uniform int instances_count;\n"
"out vec3 world_position;\n"
"void main() {\n"
" int segment = gl_VertexID / 6;\n"
" int corner = CORNERS[gl_VertexID - 6 * segment];\n"
" int instance = reverse_order ? instances_count - 1 - segment : segment;\n"
" int id_a = int(texelFetch(segment_index_tex, instance).r);\n"
" int id = corner < 2 ? id_a : id_a + 1;\n"
" vec3 pos_a = texelFetch(position_tex, id_a).xyz;\n"
" vec3 pos_b = texelFetch(position_tex, id_a + 1).xyz;\n"
" vec3 line = pos_b - pos_a;\n"
" float line_len = length(line);\n"
" vec3 line_dir = line_len < 1e-4 ? vec3(1.0, 0.0, 0.0) : line / line_len;\n"
" vec3 line_right_dir = abs(dot(line_dir, UP)) > 0.9 ? normalize(cross(vec3(1, 0, 0), line_dir)) : normalize(cross(line_dir, UP));\n"
" vec3 line_up_dir = normalize(cross(line_right_dir, line_dir));\n"
" vec3 to_light = vec3(view_matrix[0][2], view_matrix[1][2], view_matrix[2][2]);\n"
" vec3 side_dir = cross(to_light, line_dir);\n"
" side_dir = dot(side_dir, side_dir) < 1e-8 ? line_right_dir : normalize(side_dir);\n"
" vec2 height_width = texelFetch(height_width_angle_tex, id).xy;\n"
" float half_extent = 0.5 * (height_width.y * abs(dot(line_right_dir, side_dir)) + height_width.x * abs(dot(line_up_dir, side_dir)));\n"
" float along = (corner < 2 ? -0.5 : 0.5) * height_width.y;\n"
" float side = (corner == 0 || corner == 3) ? -1.0 : 1.0;\n"
" vec3 pos = (corner < 2 ? pos_a : pos_b) + along * line_dir + side * half_extent * side_dir;\n"
" world_position = pos;\n"
" gl_Position = projection_matrix * (view_matrix * vec4(pos, 1.0));\n"
"}\n";
// ORCA: realistic view - the shadow caster writes depth only. What a section cuts away casts no shadow.
static const char* Segments_Shadow_Caster_Fragment_Shader =
"#version 150\n"
"uniform vec4 clipping_plane;\n"
"in vec3 world_position;\n"
"void main() {\n"
" if (dot(vec4(world_position, 1.0), clipping_plane) < 0.0)\n"
" discard;\n"
"}\n";
static const char* Segments_Fragment_Shader =
"#version 150\n"
"// ORCA: realistic view - object-on-object and self shadows, read from the same depth map the\n"
"// rest of the 3D scene samples. shadow_intensity == 0, the default, short-circuits the lookup,\n"
"// so the toolpaths shade exactly as before whenever realistic view is off.\n"
"const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
"uniform vec3 light_top_dir;\n"
"uniform sampler2D shadow_map;\n"
"uniform mat4 shadow_light_vp;\n"
"uniform float shadow_intensity;\n"
@@ -212,7 +265,7 @@ static const char* Segments_Fragment_Shader =
" return 1.0;\n"
" // Slope-scaled bias, as in gouraud.fs. An extrusion is only a handful of shadow-map texels\n"
" // wide, so grazing faces need the larger bias to keep self-shadow acne off the top surfaces.\n"
" float NdotL = dot(normalize(normal), SHADOW_LIGHT_DIR);\n"
" float NdotL = dot(normalize(normal), light_top_dir);\n"
" float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));\n"
" float sum = 0.0;\n"
" for (int x = -2; x <= 2; ++x) {\n"
@@ -289,13 +342,22 @@ static const char* Options_Vertex_Shader =
"in vec3 in_normal;\n"
"out vec3 color;\n"
"out float clipping_plane_dot;\n"
"vec3 decode_color(float color) {\n"
// ORCA: as in Segments_Vertex_Shader.
"uniform ivec2 lit_layers;\n"
"uniform int grey_below_layer;\n"
"uniform float dim_brightness;\n"
"uniform int kept_vertex;\n"
"vec3 decode_color(float color, int id, int layer) {\n"
" int c = int(round(color));\n"
" int r = (c >> 16) & 0xFF;\n"
" int g = (c >> 8) & 0xFF;\n"
" int b = (c >> 0) & 0xFF;\n"
" vec3 rgb = vec3((c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF);\n"
" if (id != kept_vertex && layer != lit_layers.x && layer != lit_layers.y) {\n"
" if (dim_brightness >= 0.0)\n"
" rgb = floor(rgb * dim_brightness);\n"
" else if (layer < grey_below_layer)\n"
" rgb = vec3(64.0);\n"
" }\n"
" float f = 1.0 / 255.0f;\n"
" return f * vec3(r, g, b);\n"
" return f * rgb;\n"
"}\n"
"float lighting(vec3 eye_position, vec3 eye_normal) {\n"
" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n"
@@ -321,7 +383,7 @@ static const char* Options_Vertex_Shader =
" // ORCA: Apply bias to z-position to avoid z-fighting\n"
" eye_position.z += bias;\n"
" vec3 eye_normal = (view_matrix * vec4(in_normal, 0.0)).xyz;\n"
" vec3 color_base = decode_color(texelFetch(color_tex, id).r);\n"
" vec3 color_base = decode_color(texelFetch(color_tex, id).r, id, int(texelFetch(position_tex, id).w));\n"
" color = color_base * lighting(eye_position, eye_normal);\n"
" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n"
"}\n";
+96 -16
View File
@@ -15,7 +15,7 @@ static const char* Segments_Vertex_Shader_ES =
"precision lowp usampler2D;\n"
"#define POINTY_CAPS\n"
"#define FIX_TWISTING\n"
"const vec3 light_top_dir = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
"uniform vec3 light_top_dir;\n"
"const float light_top_diffuse = 0.6 * 0.8;\n"
// ORCA: the specular was 0.6 * 0.125, too faint to give the filament any sheen.
"const float light_top_specular = 0.6 * 0.25;\n"
@@ -32,6 +32,9 @@ static const char* Segments_Vertex_Shader_ES =
"uniform sampler2D height_width_angle_tex;\n"
"uniform sampler2D color_tex;\n"
"uniform usampler2D segment_index_tex;\n"
// ORCA: draw last segment first, so looking down the top layers hide the rest from the fragment shader.
"uniform bool reverse_order;\n"
"uniform int instances_count;\n"
// ORCA: section view
"uniform vec4 clipping_plane;\n"
"in float vertex_id_float;\n"
@@ -47,13 +50,22 @@ static const char* Segments_Vertex_Shader_ES =
// ORCA: section view - the segment and the light on its cut face
"flat out int segment_id;\n"
"out vec3 cut_color_direct;\n"
"vec3 decode_color(float color) {\n"
// ORCA: the layers greyed or dimmed around the one the sliders show, see ViewerImpl::set_layer_colors().
"uniform ivec2 lit_layers;\n"
"uniform int grey_below_layer;\n"
"uniform float dim_brightness;\n"
"uniform int kept_vertex;\n"
"vec3 decode_color(float color, int id, int layer) {\n"
" int c = int(round(color));\n"
" int r = (c >> 16) & 0xFF;\n"
" int g = (c >> 8) & 0xFF;\n"
" int b = (c >> 0) & 0xFF;\n"
" vec3 rgb = vec3((c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF);\n"
" if (id != kept_vertex && layer != lit_layers.x && layer != lit_layers.y) {\n"
" if (dim_brightness >= 0.0)\n"
" rgb = floor(rgb * dim_brightness);\n"
" else if (layer < grey_below_layer)\n"
" rgb = vec3(64.0);\n"
" }\n"
" float f = 1.0 / 255.0f;\n"
" return f * vec3(r, g, b);\n"
" return f * rgb;\n"
"}\n"
"float direct_lighting(vec3 eye_position, vec3 eye_normal) {\n"
" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n"
@@ -71,7 +83,8 @@ static const char* Segments_Vertex_Shader_ES =
"}\n"
"void main() {\n"
" int vertex_id = int(vertex_id_float);\n"
" int id_a = int(texelFetch(segment_index_tex, tex_coord_u(segment_index_tex, gl_InstanceID), 0).r);\n"
" int instance = reverse_order ? instances_count - 1 - gl_InstanceID : gl_InstanceID;\n"
" int id_a = int(texelFetch(segment_index_tex, tex_coord_u(segment_index_tex, instance), 0).r);\n"
" int id_b = id_a + 1;\n"
" vec3 pos_a = texelFetch(position_tex, tex_coord(position_tex, id_a), 0).xyz;\n"
" vec3 pos_b = texelFetch(position_tex, tex_coord(position_tex, id_b), 0).xyz;\n"
@@ -156,7 +169,7 @@ static const char* Segments_Vertex_Shader_ES =
" }\n"
" vec3 eye_position = (view_matrix * vec4(pos, 1.0)).xyz;\n"
" vec3 eye_normal = (view_matrix * vec4(normalize(pos - endpoint_pos), 0.0)).xyz;\n"
" vec3 color_base = decode_color(texelFetch(color_tex, tex_coord(color_tex, id), 0).r);\n"
" vec3 color_base = decode_color(texelFetch(color_tex, tex_coord(color_tex, id), 0).r, id, int(texelFetch(position_tex, tex_coord(position_tex, id), 0).w));\n"
" color = color_base * (ambient + emission);\n"
" color_direct = color_base * direct_lighting(eye_position, eye_normal);\n"
" world_position = pos;\n"
@@ -167,6 +180,64 @@ static const char* Segments_Vertex_Shader_ES =
" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n"
"}\n";
// ORCA: realistic view - shadow caster, one light facing ribbon per segment, not instanced as tiny instances are slow.
static const char* Segments_Shadow_Caster_Vertex_Shader_ES =
"#version 300 es\n"
"precision lowp usampler2D;\n"
"const vec3 UP = vec3(0, 0, 1);\n"
"const int CORNERS[6] = int[](0, 1, 2, 0, 2, 3);\n"
"uniform mat4 view_matrix;\n"
"uniform mat4 projection_matrix;\n"
"uniform sampler2D position_tex;\n"
"uniform sampler2D height_width_angle_tex;\n"
"uniform usampler2D segment_index_tex;\n"
"uniform bool reverse_order;\n"
"uniform int instances_count;\n"
"out vec3 world_position;\n"
"ivec2 tex_coord(sampler2D sampler, int id) {\n"
" ivec2 tex_size = textureSize(sampler, 0);\n"
" return (tex_size.y == 1) ? ivec2(id, 0) : ivec2(id % tex_size.x, id / tex_size.x);\n"
"}\n"
"ivec2 tex_coord_u(usampler2D sampler, int id) {\n"
" ivec2 tex_size = textureSize(sampler, 0);\n"
" return (tex_size.y == 1) ? ivec2(id, 0) : ivec2(id % tex_size.x, id / tex_size.x);\n"
"}\n"
"void main() {\n"
" int segment = gl_VertexID / 6;\n"
" int corner = CORNERS[gl_VertexID - 6 * segment];\n"
" int instance = reverse_order ? instances_count - 1 - segment : segment;\n"
" int id_a = int(texelFetch(segment_index_tex, tex_coord_u(segment_index_tex, instance), 0).r);\n"
" int id = corner < 2 ? id_a : id_a + 1;\n"
" vec3 pos_a = texelFetch(position_tex, tex_coord(position_tex, id_a), 0).xyz;\n"
" vec3 pos_b = texelFetch(position_tex, tex_coord(position_tex, id_a + 1), 0).xyz;\n"
" vec3 line = pos_b - pos_a;\n"
" float line_len = length(line);\n"
" vec3 line_dir = line_len < 1e-4 ? vec3(1.0, 0.0, 0.0) : line / line_len;\n"
" vec3 line_right_dir = abs(dot(line_dir, UP)) > 0.9 ? normalize(cross(vec3(1, 0, 0), line_dir)) : normalize(cross(line_dir, UP));\n"
" vec3 line_up_dir = normalize(cross(line_right_dir, line_dir));\n"
" vec3 to_light = vec3(view_matrix[0][2], view_matrix[1][2], view_matrix[2][2]);\n"
" vec3 side_dir = cross(to_light, line_dir);\n"
" side_dir = dot(side_dir, side_dir) < 1e-8 ? line_right_dir : normalize(side_dir);\n"
" vec2 height_width = texelFetch(height_width_angle_tex, tex_coord(height_width_angle_tex, id), 0).xy;\n"
" float half_extent = 0.5 * (height_width.y * abs(dot(line_right_dir, side_dir)) + height_width.x * abs(dot(line_up_dir, side_dir)));\n"
" float along = (corner < 2 ? -0.5 : 0.5) * height_width.y;\n"
" float side = (corner == 0 || corner == 3) ? -1.0 : 1.0;\n"
" vec3 pos = (corner < 2 ? pos_a : pos_b) + along * line_dir + side * half_extent * side_dir;\n"
" world_position = pos;\n"
" gl_Position = projection_matrix * (view_matrix * vec4(pos, 1.0));\n"
"}\n";
// ORCA: realistic view - the shadow caster writes depth only. What a section cuts away casts no shadow.
static const char* Segments_Shadow_Caster_Fragment_Shader_ES =
"#version 300 es\n"
"precision highp float;\n"
"uniform vec4 clipping_plane;\n"
"in vec3 world_position;\n"
"void main() {\n"
" if (dot(vec4(world_position, 1.0), clipping_plane) < 0.0)\n"
" discard;\n"
"}\n";
static const char* Segments_Fragment_Shader_ES =
"#version 300 es\n"
"precision highp float;\n"
@@ -178,7 +249,7 @@ static const char* Segments_Fragment_Shader_ES =
"// ORCA: realistic view - object-on-object and self shadows, read from the same depth map the\n"
"// rest of the 3D scene samples. shadow_intensity == 0, the default, short-circuits the lookup,\n"
"// so the toolpaths shade exactly as before whenever realistic view is off.\n"
"const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);\n"
"uniform vec3 light_top_dir;\n"
"uniform sampler2D shadow_map;\n"
"uniform mat4 shadow_light_vp;\n"
"uniform float shadow_intensity;\n"
@@ -215,7 +286,7 @@ static const char* Segments_Fragment_Shader_ES =
" return 1.0;\n"
" // Slope-scaled bias, as in gouraud.fs. An extrusion is only a handful of shadow-map texels\n"
" // wide, so grazing faces need the larger bias to keep self-shadow acne off the top surfaces.\n"
" float NdotL = dot(normalize(normal), SHADOW_LIGHT_DIR);\n"
" float NdotL = dot(normalize(normal), light_top_dir);\n"
" float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));\n"
" float sum = 0.0;\n"
" for (int x = -2; x <= 2; ++x) {\n"
@@ -291,13 +362,22 @@ static const char* Options_Vertex_Shader_ES =
"in vec3 in_normal;\n"
"out vec3 color;\n"
"out float clipping_plane_dot;\n"
"vec3 decode_color(float color) {\n"
// ORCA: the layers greyed or dimmed around the one the sliders show, see ViewerImpl::set_layer_colors().
"uniform ivec2 lit_layers;\n"
"uniform int grey_below_layer;\n"
"uniform float dim_brightness;\n"
"uniform int kept_vertex;\n"
"vec3 decode_color(float color, int id, int layer) {\n"
" int c = int(round(color));\n"
" int r = (c >> 16) & 0xFF;\n"
" int g = (c >> 8) & 0xFF;\n"
" int b = (c >> 0) & 0xFF;\n"
" vec3 rgb = vec3((c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF);\n"
" if (id != kept_vertex && layer != lit_layers.x && layer != lit_layers.y) {\n"
" if (dim_brightness >= 0.0)\n"
" rgb = floor(rgb * dim_brightness);\n"
" else if (layer < grey_below_layer)\n"
" rgb = vec3(64.0);\n"
" }\n"
" float f = 1.0 / 255.0f;\n"
" return f * vec3(r, g, b);\n"
" return f * rgb;\n"
"}\n"
"float lighting(vec3 eye_position, vec3 eye_normal) {\n"
" float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n"
@@ -326,7 +406,7 @@ static const char* Options_Vertex_Shader_ES =
" clipping_plane_dot = dot(vec4(offset, 1.0), clipping_plane);\n"
" vec3 eye_position = (view_matrix * vec4(scale_matrix * in_position + offset, 1.0)).xyz;\n"
" vec3 eye_normal = (view_matrix * vec4(in_normal, 0.0)).xyz;\n"
" vec3 color_base = decode_color(texelFetch(color_tex, tex_coord(color_tex, id), 0).r);\n"
" vec3 color_base = decode_color(texelFetch(color_tex, tex_coord(color_tex, id), 0).r, id, int(texelFetch(position_tex, tex_coord(position_tex, id), 0).w));\n"
" color = color_base * lighting(eye_position, eye_normal);\n"
" gl_Position = projection_matrix * vec4(eye_position, 1.0);\n"
"}\n";
+5
View File
@@ -52,6 +52,11 @@ void Viewer::set_shadow_map(int texture_unit, const Mat4x4& light_view_projectio
m_impl->set_shadow_map(texture_unit, light_view_projection, intensity, texel_size);
}
void Viewer::set_light_top_dir(const Vec3& direction)
{
m_impl->set_light_top_dir(direction);
}
void Viewer::set_tone(float exposure, float saturation)
{
m_impl->set_tone(exposure, saturation);
+159 -131
View File
@@ -349,7 +349,7 @@ void ViewerImpl::TextureData::init(size_t vertices_count)
m_tex_ids = std::vector<TexIds>(m_count);
}
void ViewerImpl::TextureData::set_positions(const std::vector<Vec3>& positions)
void ViewerImpl::TextureData::set_positions(const std::vector<Vec4>& positions)
{
if (m_count == 0)
return;
@@ -383,17 +383,17 @@ void ViewerImpl::TextureData::set_positions(const std::vector<Vec3>& positions)
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
if (remaining >= tex_capacity) {
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGB, GL_FLOAT, &positions[offset]));
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGBA, GL_FLOAT, &positions[offset]));
m_tex_ids[i].positions.second = w * h;
}
else {
// the last row is only partially fitted with data, send it separately
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGB, GL_FLOAT, nullptr));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(w), static_cast<GLsizei>(h - 1), GL_RGB, GL_FLOAT, &positions[offset]));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, static_cast<GLsizei>(h - 1), static_cast<GLsizei>(remaining % w), 1, GL_RGB, GL_FLOAT, &positions[offset + w * (h - 1)]));
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGBA, GL_FLOAT, nullptr));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(w), static_cast<GLsizei>(h - 1), GL_RGBA, GL_FLOAT, &positions[offset]));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, static_cast<GLsizei>(h - 1), static_cast<GLsizei>(remaining % w), 1, GL_RGBA, GL_FLOAT, &positions[offset + w * (h - 1)]));
m_tex_ids[i].positions.second = w * (h - 1) + remaining % w;
}
m_positions_size += m_tex_ids[i].positions.second * sizeof(Vec3);
m_positions_size += m_tex_ids[i].positions.second * sizeof(Vec4);
remaining = (remaining > tex_capacity) ? remaining - tex_capacity: 0;
}
@@ -402,7 +402,7 @@ void ViewerImpl::TextureData::set_positions(const std::vector<Vec3>& positions)
glsafe(glPixelStorei(GL_UNPACK_ALIGNMENT, curr_unpack_alignment));
}
void ViewerImpl::TextureData::set_heights_widths_angles(const std::vector<Vec3>& heights_widths_angles)
void ViewerImpl::TextureData::set_heights_widths_angles(const std::vector<Vec4>& heights_widths_angles)
{
if (m_count == 0)
return;
@@ -436,17 +436,17 @@ void ViewerImpl::TextureData::set_heights_widths_angles(const std::vector<Vec3>&
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
glsafe(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
if (remaining >= tex_capacity) {
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGB, GL_FLOAT, &heights_widths_angles[offset]));
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGBA, GL_FLOAT, &heights_widths_angles[offset]));
m_tex_ids[i].heights_widths_angles.second = w * h;
}
else {
// the last row is only partially fitted with data, send it separately
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGB, GL_FLOAT, nullptr));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(w), static_cast<GLsizei>(h - 1), GL_RGB, GL_FLOAT, &heights_widths_angles[offset]));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, static_cast<GLsizei>(h - 1), static_cast<GLsizei>(remaining % w), 1, GL_RGB, GL_FLOAT, &heights_widths_angles[offset + w * (h - 1)]));
glsafe(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast<GLsizei>(w), static_cast<GLsizei>(h), 0, GL_RGBA, GL_FLOAT, nullptr));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(w), static_cast<GLsizei>(h - 1), GL_RGBA, GL_FLOAT, &heights_widths_angles[offset]));
glsafe(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, static_cast<GLsizei>(h - 1), static_cast<GLsizei>(remaining % w), 1, GL_RGBA, GL_FLOAT, &heights_widths_angles[offset + w * (h - 1)]));
m_tex_ids[i].heights_widths_angles.second = w * (h - 1) + remaining % w;
}
m_height_width_angle_size += m_tex_ids[i].heights_widths_angles.second * sizeof(Vec3);
m_height_width_angle_size += m_tex_ids[i].heights_widths_angles.second * sizeof(Vec4);
remaining = (remaining > tex_capacity) ? remaining - tex_capacity : 0;
}
@@ -732,6 +732,21 @@ ViewerImpl::ViewerImpl()
reset_default_options_colors();
}
void ViewerImpl::SegmentsUniforms::init(unsigned int shader_id)
{
view_matrix = glGetUniformLocation(shader_id, "view_matrix");
projection_matrix = glGetUniformLocation(shader_id, "projection_matrix");
camera_position = glGetUniformLocation(shader_id, "camera_position");
positions_tex = glGetUniformLocation(shader_id, "position_tex");
height_width_angle_tex = glGetUniformLocation(shader_id, "height_width_angle_tex");
colors_tex = glGetUniformLocation(shader_id, "color_tex");
segment_index_tex = glGetUniformLocation(shader_id, "segment_index_tex");
reverse_order = glGetUniformLocation(shader_id, "reverse_order");
instances_count = glGetUniformLocation(shader_id, "instances_count");
// ORCA: section view
clipping_plane = glGetUniformLocation(shader_id, "clipping_plane");
}
void ViewerImpl::init(const std::string& opengl_context_version)
{
if (m_initialized)
@@ -756,15 +771,7 @@ void ViewerImpl::init(const std::string& opengl_context_version)
m_segments_shader_id = init_shader("segments", Segments_Vertex_Shader, Segments_Fragment_Shader);
#endif // ENABLE_OPENGL_ES
m_uni_segments_view_matrix_id = glGetUniformLocation(m_segments_shader_id, "view_matrix");
m_uni_segments_projection_matrix_id = glGetUniformLocation(m_segments_shader_id, "projection_matrix");
m_uni_segments_camera_position_id = glGetUniformLocation(m_segments_shader_id, "camera_position");
m_uni_segments_positions_tex_id = glGetUniformLocation(m_segments_shader_id, "position_tex");
m_uni_segments_height_width_angle_tex_id = glGetUniformLocation(m_segments_shader_id, "height_width_angle_tex");
m_uni_segments_colors_tex_id = glGetUniformLocation(m_segments_shader_id, "color_tex");
m_uni_segments_segment_index_tex_id = glGetUniformLocation(m_segments_shader_id, "segment_index_tex");
m_uni_segments_reverse_order_id = glGetUniformLocation(m_segments_shader_id, "reverse_order");
m_uni_segments_instance_count_id = glGetUniformLocation(m_segments_shader_id, "instance_count");
m_uni_segments.init(m_segments_shader_id);
// ORCA: realistic view
m_uni_segments_shadow_map_id = glGetUniformLocation(m_segments_shader_id, "shadow_map");
m_uni_segments_shadow_light_vp_id = glGetUniformLocation(m_segments_shader_id, "shadow_light_vp");
@@ -772,17 +779,29 @@ void ViewerImpl::init(const std::string& opengl_context_version)
m_uni_segments_shadow_map_texel_id = glGetUniformLocation(m_segments_shader_id, "shadow_map_texel");
m_uni_segments_exposure_id = glGetUniformLocation(m_segments_shader_id, "exposure");
m_uni_segments_saturation_id = glGetUniformLocation(m_segments_shader_id, "saturation");
m_uni_segments_bias_scale_id = glGetUniformLocation(m_segments_shader_id, "bias_scale");
// ORCA: section view
m_uni_segments_clipping_plane_id = glGetUniformLocation(m_segments_shader_id, "clipping_plane");
m_uni_segments_light_top_dir_id = glGetUniformLocation(m_segments_shader_id, "light_top_dir");
m_uni_segments_layer_colors.init(m_segments_shader_id);
glcheck();
assert(m_uni_segments.view_matrix != -1 &&
m_uni_segments.projection_matrix != -1 &&
m_uni_segments.camera_position != -1 &&
m_uni_segments.positions_tex != -1 &&
m_uni_segments.height_width_angle_tex != -1 &&
m_uni_segments.colors_tex != -1 &&
m_uni_segments.segment_index_tex != -1 &&
m_uni_segments.reverse_order != -1 &&
m_uni_segments.instances_count != -1);
// ORCA: realistic view
#ifdef ENABLE_OPENGL_ES
m_segments_caster_shader_id = init_shader("segments_shadow_caster", Segments_Shadow_Caster_Vertex_Shader_ES, Segments_Shadow_Caster_Fragment_Shader_ES);
#else
m_segments_caster_shader_id = init_shader("segments_shadow_caster", Segments_Shadow_Caster_Vertex_Shader, Segments_Shadow_Caster_Fragment_Shader);
#endif // ENABLE_OPENGL_ES
m_uni_segments_caster.init(m_segments_caster_shader_id);
// The caster pulls its vertices from gl_VertexID, but a core profile needs a vertex array bound to draw.
glsafe(glGenVertexArrays(1, &m_segments_caster_vao_id));
glcheck();
assert(m_uni_segments_view_matrix_id != -1 &&
m_uni_segments_projection_matrix_id != -1 &&
m_uni_segments_camera_position_id != -1 &&
m_uni_segments_positions_tex_id != -1 &&
m_uni_segments_height_width_angle_tex_id != -1 &&
m_uni_segments_colors_tex_id != -1 &&
m_uni_segments_segment_index_tex_id != -1);
m_segment_template.init();
@@ -801,6 +820,7 @@ void ViewerImpl::init(const std::string& opengl_context_version)
m_uni_options_segment_index_tex_id = glGetUniformLocation(m_options_shader_id, "segment_index_tex");
// ORCA: section view
m_uni_options_clipping_plane_id = glGetUniformLocation(m_options_shader_id, "clipping_plane");
m_uni_options_layer_colors.init(m_options_shader_id);
glcheck();
assert(m_uni_options_view_matrix_id != -1 &&
m_uni_options_projection_matrix_id != -1 &&
@@ -874,6 +894,14 @@ void ViewerImpl::shutdown()
glsafe(glDeleteProgram(m_segments_shader_id));
m_segments_shader_id = 0;
}
if (m_segments_caster_shader_id != 0) {
glsafe(glDeleteProgram(m_segments_caster_shader_id));
m_segments_caster_shader_id = 0;
}
if (m_segments_caster_vao_id != 0) {
glsafe(glDeleteVertexArrays(1, &m_segments_caster_vao_id));
m_segments_caster_vao_id = 0;
}
m_initialized = false;
OpenGLWrapper::unload_opengl();
}
@@ -894,7 +922,7 @@ void ViewerImpl::reset()
for (std::vector<float>& times : m_layer_start_times)
std::vector<float>().swap(times);
std::vector<uint32_t>().swap(m_layer_first_vertex);
std::vector<float>().swap(m_colors_scratch);
m_layers_in_vertex_order = false;
m_valid_lines_bitset.clear();
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
m_cog_marker.reset();
@@ -924,9 +952,8 @@ void ViewerImpl::reset()
// On some graphic cards texture buffers using GL_RGB32F format do not work, see:
// https://dev.prusa3d.com/browse/SPE-2411
// https://github.com/prusa3d/PrusaSlicer/issues/12908
// To let all drivers be happy, we use GL_RGBA32F format, so we need to add an extra (currently unused) float
// To let all drivers be happy, we use GL_RGBA32F format, so we need to add an extra float
// to position and heights_widths_angles vectors
using Vec4 = std::array<float, 4>;
static void extract_pos_and_or_hwa(const std::vector<PathVertex>& vertices, float travels_radius, float wipes_radius, BitSet<>& valid_lines_bitset,
std::vector<Vec4>* positions = nullptr, std::vector<Vec4>* heights_widths_angles = nullptr, bool update_bitset = false) {
@@ -963,8 +990,8 @@ static void extract_pos_and_or_hwa(const std::vector<PathVertex>& vertices, floa
}
if (positions != nullptr) {
// the last component is a dummy float to comply with GL_RGBA32F format
Vec4 position = { v.position[0], v.position[1], v.position[2], 0.0f };
// ORCA: the last component, there to comply with GL_RGBA32F format, carries the layer the shaders grey and dim by
Vec4 position = { v.position[0], v.position[1], v.position[2], static_cast<float>(v.layer_id) };
if (move_type == EMoveType::Extrude)
// push down extrusion vertices by half height to render them at the right z
position[2] -= 0.5f * v.height;
@@ -1080,6 +1107,8 @@ void ViewerImpl::load(GCodeInputData&& gcode_data)
}
for (size_t i = m_layer_first_vertex.size() - 1; i > 0; --i)
m_layer_first_vertex[i - 1] = std::min(m_layer_first_vertex[i - 1], m_layer_first_vertex[i]);
m_layers_in_vertex_order = std::is_sorted(m_vertices.begin(), m_vertices.end(),
[](const PathVertex& a, const PathVertex& b) { return a.layer_id < b.layer_id; });
// the running time at each layer's first vertex, summed in vertex order so that
// get_estimated_time_at() matches a full accumulation exactly
@@ -1274,17 +1303,6 @@ static float encode_color(const Color& color) {
return static_cast<float>(i_color);
}
// ORCA: returns the encoded color scaled towards black by 'brightness', preserving its hue.
// 1.0 = no change, 0.0 = black.
static float encode_color_dimmed(const Color& color, float brightness) {
const int r = static_cast<int>(color[0] * brightness);
const int g = static_cast<int>(color[1] * brightness);
const int b = static_cast<int>(color[2] * brightness);
const int i_color = r << 16 | g << 8 | b;
return static_cast<float>(i_color);
}
void ViewerImpl::update_colors_texture()
{
#if !defined(ENABLE_OPENGL_ES)
@@ -1292,6 +1310,32 @@ void ViewerImpl::update_colors_texture()
return;
#endif // ENABLE_OPENGL_ES
// ORCA: the colors of the vertices alone; the layers greyed or dimmed around the one the sliders
// show are left to the shaders, see set_layer_colors(), so that a slider step uploads nothing.
#ifdef ENABLE_OPENGL_ES
if (!m_vertices_colors.empty())
// update gpu buffer for colors
m_texture_data.set_colors(m_vertices_colors);
#else
m_colors_tex_size = m_vertices_colors.size() * sizeof(float);
// update gpu buffer for colors
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_colors_buf_id));
glsafe(glBufferData(GL_TEXTURE_BUFFER, m_vertices_colors.size() * sizeof(float), m_vertices_colors.data(), GL_STATIC_DRAW));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
#endif // ENABLE_OPENGL_ES
}
void ViewerImpl::LayerColorsUniforms::init(unsigned int shader_id)
{
lit_layers = glGetUniformLocation(shader_id, "lit_layers");
grey_below_layer = glGetUniformLocation(shader_id, "grey_below_layer");
dim_brightness = glGetUniformLocation(shader_id, "dim_brightness");
kept_vertex = glGetUniformLocation(shader_id, "kept_vertex");
}
void ViewerImpl::set_layer_colors(const LayerColorsUniforms& uni) const
{
const size_t top_layer_id = m_settings.top_layer_only_view_range ? m_layers.get_view_range()[1] : 0;
const bool color_top_layer_only = m_view_range.get_full()[1] != m_view_range.get_visible()[1];
@@ -1310,43 +1354,15 @@ void ViewerImpl::update_colors_texture()
const bool dim_previous_layers = m_settings.dim_previous_layers && m_settings.top_layer_only_view_range &&
!m_layers.empty() && (inspecting_top_layer || inspecting_bottom_layer);
// Based on current settings and slider position, we might want to render some
// vertices as dark grey (or darkened, see above). Use either that or the normal color (from the cache).
// Reused across calls: this runs on every slider tick, and the allocation alone is
// 4 bytes per vertex of the whole print each time.
std::vector<float>& colors = m_colors_scratch;
colors.resize(m_vertices_colors.size());
assert(colors.size() == m_vertices.size() && m_vertices_colors.size() == m_vertices.size());
for (size_t i=0; i<m_vertices.size(); ++i) {
const PathVertex& v = m_vertices[i];
const bool keep_spiral_seam = m_settings.spiral_vase_mode && i == m_view_range.get_enabled()[0];
// ORCA: layers kept at full brightness by the dimming above are excluded from the greying below too
const bool inspected_layer = dim_previous_layers &&
((inspecting_top_layer && v.layer_id == layers_range[1]) ||
(inspecting_bottom_layer && v.layer_id == layers_range[0]));
if (dim_previous_layers && !inspected_layer && !keep_spiral_seam)
colors[i] = encode_color_dimmed(get_vertex_color(v), m_settings.dim_previous_layers_brightness);
else if (!inspected_layer && color_top_layer_only && v.layer_id < top_layer_id && !keep_spiral_seam)
colors[i] = encode_color(DUMMY_COLOR);
else
colors[i] = m_vertices_colors[i];
}
#ifdef ENABLE_OPENGL_ES
if (!colors.empty())
// update gpu buffer for colors
m_texture_data.set_colors(colors);
#else
m_colors_tex_size = colors.size() * sizeof(float);
// update gpu buffer for colors
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, m_colors_buf_id));
glsafe(glBufferData(GL_TEXTURE_BUFFER, colors.size() * sizeof(float), colors.data(), GL_STATIC_DRAW));
glsafe(glBindBuffer(GL_TEXTURE_BUFFER, 0));
#endif // ENABLE_OPENGL_ES
// Without dimming, the layers below the top one go dark grey while the moves slider is short of
// the end. Neither touches the inspected layers, nor the first vertex of a spiral vase layer.
glsafe(glUniform2i(uni.lit_layers, dim_previous_layers && inspecting_bottom_layer ? static_cast<int>(layers_range[0]) : -1,
dim_previous_layers && inspecting_top_layer ? static_cast<int>(layers_range[1]) : -1));
glsafe(glUniform1i(uni.grey_below_layer, color_top_layer_only ? static_cast<int>(top_layer_id) : 0));
glsafe(glUniform1f(uni.dim_brightness, dim_previous_layers ? m_settings.dim_previous_layers_brightness : -1.0f));
glsafe(glUniform1i(uni.kept_vertex, m_settings.spiral_vase_mode ? static_cast<int>(m_view_range.get_enabled()[0]) : -1));
}
void ViewerImpl::update_colors()
{
@@ -1366,7 +1382,7 @@ void ViewerImpl::update_colors()
// Recalculate "normal" colors of all the vertices for current view settings.
// If some part of the preview should be rendered in dark grey, it is taken
// care of in update_colors_texture. That is to avoid the need to recalculate
// care of in the shaders, see set_layer_colors(). That is to avoid the need to recalculate
// the "normal" color on every slider move.
for (size_t i = 0; i < m_vertices.size(); ++i)
m_vertices_colors[i] = encode_color(get_vertex_color(m_vertices[i]));
@@ -1453,8 +1469,6 @@ void ViewerImpl::set_layers_view_range(Interval::value_type min, Interval::value
update_view_full_range();
m_view_range.set_visible(m_view_range.get_enabled());
m_settings.update_enabled_entities = true;
//m_settings.update_colors = true;
update_colors_texture();
}
void ViewerImpl::toggle_top_layer_only_view_range()
@@ -1463,8 +1477,6 @@ void ViewerImpl::toggle_top_layer_only_view_range()
update_view_full_range();
m_view_range.set_visible(m_view_range.get_enabled());
m_settings.update_enabled_entities = true;
//m_settings.update_colors = true;
update_colors_texture();
}
// ORCA: enable/disable darkening of the layers the layer slider is not scrubbed to
@@ -1473,9 +1485,6 @@ void ViewerImpl::set_dim_previous_layers(bool value)
if (m_settings.dim_previous_layers == value)
return;
m_settings.dim_previous_layers = value;
// defer the actual color/texture rebuild to the next render(), when the GL context is current
// (this may be toggled from the Preferences dialog, outside the canvas context)
m_settings.update_colors = true;
}
// ORCA: set how bright the darkened layers are rendered, 1.0 = unchanged, 0.0 = black
@@ -1485,7 +1494,6 @@ void ViewerImpl::set_dim_previous_layers_brightness(float value)
if (m_settings.dim_previous_layers_brightness == value)
return;
m_settings.dim_previous_layers_brightness = value;
m_settings.update_colors = true;
}
std::vector<ETimeMode> ViewerImpl::get_time_modes() const
@@ -1593,8 +1601,6 @@ void ViewerImpl::set_view_visible_range(Interval::value_type min, Interval::valu
update_view_full_range();
m_view_range.set_visible(min, max);
update_enabled_entities();
//m_settings.update_colors = true;
update_colors_texture();
}
float ViewerImpl::get_estimated_time_at(size_t id) const
@@ -1819,7 +1825,6 @@ size_t ViewerImpl::get_used_cpu_memory() const
for (const std::vector<float>& times : m_layer_start_times)
ret += STDVEC_MEMSIZE(times, float);
ret += STDVEC_MEMSIZE(m_layer_first_vertex, uint32_t);
ret += STDVEC_MEMSIZE(m_colors_scratch, float);
ret += m_valid_lines_bitset.size_in_bytes_cpu();
ret += m_height_range.size_in_bytes_cpu();
ret += m_width_range.size_in_bytes_cpu();
@@ -1912,6 +1917,11 @@ void ViewerImpl::update_view_full_range()
}
auto last_it = first_it;
// ORCA: skip straight to the layer above the range rather than walking there
if (m_layers_in_vertex_order && layers_range[1] + 1 < m_layer_first_vertex.size())
last_it = std::max(first_it, m_vertices.begin() + m_layer_first_vertex[layers_range[1] + 1]);
else if (m_layers_in_vertex_order)
last_it = m_vertices.end();
while (last_it != m_vertices.end() && last_it->layer_id <= layers_range[1]) {
++last_it;
}
@@ -1951,12 +1961,14 @@ void ViewerImpl::update_view_full_range()
if (m_settings.top_layer_only_view_range) {
const Interval& full_range = m_view_range.get_full();
auto top_first_it = m_vertices.begin() + full_range[0];
bool shortened = false;
while (top_first_it != m_vertices.end() && (top_first_it->layer_id < layers_range[1] || !is_visible(*top_first_it, m_settings))) {
const auto range_first_it = m_vertices.begin() + full_range[0];
auto top_first_it = range_first_it;
// ORCA: skip straight to the top layer rather than walking there
if (m_layers_in_vertex_order)
top_first_it = std::max(top_first_it, m_vertices.begin() + m_layer_first_vertex[layers_range[1]]);
while (top_first_it != m_vertices.end() && (top_first_it->layer_id < layers_range[1] || !is_visible(*top_first_it, m_settings)))
++top_first_it;
shortened = true;
}
const bool shortened = top_first_it != range_first_it;
if (shortened)
--top_first_it;
@@ -2038,7 +2050,7 @@ void ViewerImpl::update_color_ranges()
void ViewerImpl::update_heights_widths()
{
#ifdef ENABLE_OPENGL_ES
std::vector<Vec3> heights_widths_angles;
std::vector<Vec4> heights_widths_angles;
heights_widths_angles.reserve(m_vertices.size());
extract_pos_and_or_hwa(m_vertices, m_travels_radius, m_wipes_radius, m_valid_lines_bitset, nullptr, &heights_widths_angles);
m_texture_data.set_heights_widths_angles(heights_widths_angles);
@@ -2070,7 +2082,10 @@ void ViewerImpl::update_heights_widths()
void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projection_matrix, const Vec3& camera_position)
{
if (m_segments_shader_id == 0)
// ORCA: realistic view. The shadow caster pass draws depth only.
const unsigned int shader_id = m_rendering_shadow_casters ? m_segments_caster_shader_id : m_segments_shader_id;
const SegmentsUniforms& uni = m_rendering_shadow_casters ? m_uni_segments_caster : m_uni_segments;
if (shader_id == 0)
return;
#ifdef ENABLE_OPENGL_ES
@@ -2087,42 +2102,54 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
const bool curr_cull_face = glIsEnabled(GL_CULL_FACE);
glcheck();
glsafe(glUseProgram(m_segments_shader_id));
glsafe(glUseProgram(shader_id));
glsafe(glUniform1i(m_uni_segments_positions_tex_id, 0));
glsafe(glUniform1i(m_uni_segments_height_width_angle_tex_id, 1));
glsafe(glUniform1i(m_uni_segments_colors_tex_id, 2));
glsafe(glUniform1i(m_uni_segments_segment_index_tex_id, 3));
glsafe(glUniformMatrix4fv(m_uni_segments_view_matrix_id, 1, GL_FALSE, view_matrix.data()));
glsafe(glUniformMatrix4fv(m_uni_segments_projection_matrix_id, 1, GL_FALSE, projection_matrix.data()));
glsafe(glUniform3fv(m_uni_segments_camera_position_id, 1, camera_position.data()));
// The segments come in print order, bottom layer first. Seen from above, that is back to front,
// and every hidden fragment is shaded before the one that covers it. Drawing them last to first
// lets the depth test reject the hidden ones instead. The camera looks down when the world's
// up axis points towards it, which is the view matrix's (2, 2) entry being positive.
const bool top_down = !m_rendering_shadow_casters && view_matrix[10] > 0.0f;
glsafe(glUniform1i(m_uni_segments_reverse_order_id, top_down ? 1 : 0));
#ifndef ENABLE_OPENGL_ES
glsafe(glUniform1i(m_uni_segments_instance_count_id, static_cast<int>(m_enabled_segments_count)));
#endif // ENABLE_OPENGL_ES
// ORCA: realistic view. The depth pass writes the map it would otherwise read, so it shades
// with the lookup off.
glsafe(glUniform1i(m_uni_segments_shadow_map_id, m_shadow_map_texture_unit));
glsafe(glUniformMatrix4fv(m_uni_segments_shadow_light_vp_id, 1, GL_FALSE, m_shadow_light_vp.data()));
glsafe(glUniform1f(m_uni_segments_shadow_intensity_id, m_rendering_shadow_casters ? 0.0f : m_shadow_intensity));
glsafe(glUniform1f(m_uni_segments_shadow_map_texel_id, m_shadow_map_texel));
glsafe(glUniform1f(m_uni_segments_exposure_id, m_exposure));
glsafe(glUniform1f(m_uni_segments_saturation_id, m_saturation));
glsafe(glUniform1f(m_uni_segments_bias_scale_id, m_rendering_shadow_casters ? 0.0f : 1.0f));
glsafe(glUniform4fv(m_uni_segments_clipping_plane_id, 1, m_clipping_plane.data()));
glsafe(glUniform1i(uni.positions_tex, 0));
glsafe(glUniform1i(uni.height_width_angle_tex, 1));
glsafe(glUniform1i(uni.colors_tex, 2));
glsafe(glUniform1i(uni.segment_index_tex, 3));
glsafe(glUniformMatrix4fv(uni.view_matrix, 1, GL_FALSE, view_matrix.data()));
glsafe(glUniformMatrix4fv(uni.projection_matrix, 1, GL_FALSE, projection_matrix.data()));
glsafe(glUniform3fv(uni.camera_position, 1, camera_position.data()));
// ORCA: view_matrix(2,2) > 0 is looking down, where the last segments printed are the nearest.
const bool reverse_order = view_matrix[10] > 0.0f;
glsafe(glUniform1i(uni.reverse_order, reverse_order ? 1 : 0));
glsafe(glUniform4fv(uni.clipping_plane, 1, m_clipping_plane.data()));
if (!m_rendering_shadow_casters) {
// ORCA: realistic view
glsafe(glUniform1i(m_uni_segments_shadow_map_id, m_shadow_map_texture_unit));
glsafe(glUniformMatrix4fv(m_uni_segments_shadow_light_vp_id, 1, GL_FALSE, m_shadow_light_vp.data()));
glsafe(glUniform1f(m_uni_segments_shadow_intensity_id, m_shadow_intensity));
glsafe(glUniform1f(m_uni_segments_shadow_map_texel_id, m_shadow_map_texel));
glsafe(glUniform1f(m_uni_segments_exposure_id, m_exposure));
glsafe(glUniform1f(m_uni_segments_saturation_id, m_saturation));
glsafe(glUniform3fv(m_uni_segments_light_top_dir_id, 1, m_light_top_dir.data()));
set_layer_colors(m_uni_segments_layer_colors);
}
glsafe(glDisable(GL_CULL_FACE));
auto draw = [this, &uni](size_t count) {
glsafe(glUniform1i(uni.instances_count, static_cast<int>(count)));
if (!m_rendering_shadow_casters) {
m_segment_template.render(count);
return;
}
// ORCA: realistic view. 6 vertices per caster ribbon.
int curr_vertex_array = 0;
glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array));
glsafe(glBindVertexArray(m_segments_caster_vao_id));
glsafe(glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(6 * count)));
glsafe(glBindVertexArray(curr_vertex_array));
};
#ifdef ENABLE_OPENGL_ES
int curr_bound_texture = 0;
glsafe(glGetIntegerv(GL_TEXTURE_BINDING_2D, &curr_bound_texture));
for (size_t i = 0; i < m_texture_data.get_count(); ++i) {
const size_t tex_count = m_texture_data.get_count();
for (size_t n = 0; n < tex_count; ++n) {
const size_t i = reverse_order ? tex_count - 1 - n : n;
const auto [id, count] = m_texture_data.get_enabled_segments_tex_id(i);
if (count == 0)
continue;
@@ -2134,7 +2161,7 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
glsafe(glBindTexture(GL_TEXTURE_2D, m_texture_data.get_colors_tex_id(i).first));
glsafe(glActiveTexture(GL_TEXTURE3));
glsafe(glBindTexture(GL_TEXTURE_2D, id));
m_segment_template.render(count);
draw(count);
}
#else
std::array<int, 4> curr_bound_texture = { 0, 0, 0, 0 };
@@ -2157,7 +2184,7 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
glsafe(glBindTexture(GL_TEXTURE_BUFFER, m_enabled_segments_tex_id));
glsafe(glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, m_enabled_segments_buf_id));
m_segment_template.render(m_enabled_segments_count);
draw(m_enabled_segments_count);
#endif // ENABLE_OPENGL_ES
if (curr_cull_face)
@@ -2203,6 +2230,7 @@ void ViewerImpl::render_options(const Mat4x4& view_matrix, const Mat4x4& project
glsafe(glUniformMatrix4fv(m_uni_options_view_matrix_id, 1, GL_FALSE, view_matrix.data()));
glsafe(glUniformMatrix4fv(m_uni_options_projection_matrix_id, 1, GL_FALSE, projection_matrix.data()));
glsafe(glUniform4fv(m_uni_options_clipping_plane_id, 1, m_clipping_plane.data()));
set_layer_colors(m_uni_options_layer_colors);
glsafe(glEnable(GL_CULL_FACE));
+46 -18
View File
@@ -91,6 +91,7 @@ public:
void set_tone(float exposure, float saturation);
// ORCA: section view, see Viewer::set_clipping_plane()
void set_clipping_plane(const std::array<float, 4>& plane) { m_clipping_plane = plane; }
void set_light_top_dir(const Vec3& direction) { m_light_top_dir = direction; }
EViewType get_view_type() const { return m_settings.view_type; }
void set_view_type(EViewType type);
@@ -264,9 +265,10 @@ private:
//
std::vector<uint32_t> m_layer_first_vertex;
//
// Scratch buffer for update_colors_texture(), kept alive across slider steps
// ORCA: whether the layer ids never decrease along the vertices, so that each layer's vertices
// follow m_layer_first_vertex. Not so for a print by object, whose layers start over per object.
//
std::vector<float> m_colors_scratch;
bool m_layers_in_vertex_order{ false };
//
// Detected travel moves times
//
@@ -349,6 +351,9 @@ private:
// OpenGL shaders ids
//
unsigned int m_segments_shader_id{ 0 };
// ORCA: realistic view. Depth-only ribbons for the shadow caster pass, and the empty vertex array they draw with.
unsigned int m_segments_caster_shader_id{ 0 };
unsigned int m_segments_caster_vao_id{ 0 };
unsigned int m_options_shader_id{ 0 };
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
unsigned int m_cog_marker_shader_id{ 0 };
@@ -357,25 +362,44 @@ private:
//
// Caches for OpenGL uniforms id for segments shader
//
int m_uni_segments_view_matrix_id{ -1 };
int m_uni_segments_projection_matrix_id{ -1 };
int m_uni_segments_camera_position_id{ -1 };
int m_uni_segments_positions_tex_id{ -1 };
int m_uni_segments_height_width_angle_tex_id{ -1 };
int m_uni_segments_colors_tex_id{ -1 };
int m_uni_segments_segment_index_tex_id{ -1 };
int m_uni_segments_reverse_order_id{ -1 };
int m_uni_segments_instance_count_id{ -1 };
// ORCA: the ones the shaded and the shadow caster programs share.
struct SegmentsUniforms
{
int view_matrix{ -1 };
int projection_matrix{ -1 };
int camera_position{ -1 };
int positions_tex{ -1 };
int height_width_angle_tex{ -1 };
int colors_tex{ -1 };
int segment_index_tex{ -1 };
int reverse_order{ -1 };
int instances_count{ -1 };
int clipping_plane{ -1 };
void init(unsigned int shader_id);
};
SegmentsUniforms m_uni_segments;
SegmentsUniforms m_uni_segments_caster;
int m_uni_segments_shadow_map_id{ -1 };
int m_uni_segments_shadow_light_vp_id{ -1 };
int m_uni_segments_shadow_intensity_id{ -1 };
int m_uni_segments_shadow_map_texel_id{ -1 };
int m_uni_segments_exposure_id{ -1 };
int m_uni_segments_saturation_id{ -1 };
int m_uni_segments_bias_scale_id{ -1 };
int m_uni_segments_clipping_plane_id{ -1 };
int m_uni_segments_light_top_dir_id{ -1 };
// ORCA: the layers greyed or dimmed around the one the sliders show, in the segments and options shaders.
struct LayerColorsUniforms
{
int lit_layers{ -1 };
int grey_below_layer{ -1 };
int dim_brightness{ -1 };
int kept_vertex{ -1 };
void init(unsigned int shader_id);
};
LayerColorsUniforms m_uni_segments_layer_colors;
//
// Caches for OpenGL uniforms id for options shader
// Caches for OpenGL uniforms id for options shader
//
int m_uni_options_view_matrix_id{ -1 };
int m_uni_options_projection_matrix_id{ -1 };
@@ -384,6 +408,7 @@ private:
int m_uni_options_colors_tex_id{ -1 };
int m_uni_options_segment_index_tex_id{ -1 };
int m_uni_options_clipping_plane_id{ -1 };
LayerColorsUniforms m_uni_options_layer_colors;
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
//
// Caches for OpenGL uniforms id for cog marker shader
@@ -407,8 +432,8 @@ private:
{
public:
void init(size_t vertices_count);
void set_positions(const std::vector<Vec3>& positions);
void set_heights_widths_angles(const std::vector<Vec3>& heights_widths_angles);
void set_positions(const std::vector<Vec4>& positions);
void set_heights_widths_angles(const std::vector<Vec4>& heights_widths_angles);
void set_colors(const std::vector<float>& colors);
void set_enabled_segments(const std::vector<uint32_t>& enabled_segments);
void set_enabled_options(const std::vector<uint32_t>& enabled_options);
@@ -516,8 +541,7 @@ private:
//
// ORCA: realistic view. Shadow map state set by set_shadow_map(), consumed by the segments
// shader. m_rendering_shadow_casters forces the intensity to 0 for the depth pass, which
// must not sample the very map it is writing.
// shader. m_rendering_shadow_casters switches render_segments() to the depth-only program.
//
// Defaults past the four texture units render_segments() binds itself, so the sampler never
// aliases one of the buffer textures before the owner of the map has said where it lives.
@@ -533,6 +557,8 @@ private:
//
float m_exposure{ 1.0f };
float m_saturation{ 1.0f };
// ORCA: the light the segments shader shades with, in eye space.
Vec3 m_light_top_dir{ -0.4574957f, 0.4574957f, 0.7624929f };
// ORCA: section view
std::array<float, 4> m_clipping_plane{ 0.0f, 0.0f, 0.0f, 1.0f };
@@ -543,6 +569,8 @@ private:
void update_heights_widths();
void render_segments(const Mat4x4& view_matrix, const Mat4x4& projection_matrix, const Vec3& camera_position);
void render_options(const Mat4x4& view_matrix, const Mat4x4& projection_matrix);
// ORCA: the program using uni must be current.
void set_layer_colors(const LayerColorsUniforms& uni) const;
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
void render_cog_marker(const Mat4x4& view_matrix, const Mat4x4& projection_matrix);
void render_tool_marker(const Mat4x4& view_matrix, const Mat4x4& projection_matrix);
+4
View File
@@ -503,6 +503,10 @@ set(SLIC3R_GUI_SOURCES
GUI/SceneRaycaster.hpp
GUI/SceneCache.cpp
GUI/SceneCache.hpp
GUI/FrameProfiler.cpp
GUI/FrameProfiler.hpp
GUI/SceneBenchmark.cpp
GUI/SceneBenchmark.hpp
GUI/PartSkipCommon.hpp
GUI/PartSkipDialog.cpp
GUI/PartSkipDialog.hpp
+10 -1
View File
@@ -1127,8 +1127,17 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo
);
}
else if (type == GLVolumeCollection::ERenderType::Opaque && list.size() > 1) {
// Orca: nearest first after the selected ones, so the depth test skips shading hidden surfaces.
for (GLVolumeWithIdAndZ& volume : list) {
volume.second.second = volume.first->transformed_bounding_box().transformed(view_matrix).max(2);
}
std::sort(list.begin(), list.end(),
[](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool { return v1.first->selected && !v2.first->selected; }
[](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool {
if (v1.first->selected != v2.first->selected)
return v1.first->selected;
return v1.second.second > v2.second.second;
}
);
}
+153
View File
@@ -0,0 +1,153 @@
#include "libslic3r/libslic3r.h"
#include "FrameProfiler.hpp"
#include "3DScene.hpp"
#include <glad/gl.h>
#include <algorithm>
#include <cstring>
namespace Slic3r {
namespace GUI {
void FrameProfiler::begin_frame()
{
m_recording = nullptr;
// GL 3.3 or ARB_timer_query
if (glQueryCounter == nullptr)
return;
collect(false);
Frame& frame = m_frames[m_next];
// Every frame in flight still waits for the GPU: skip this one rather than stall.
if (frame.pending)
return;
if (frame.queries[0] == 0)
glsafe(::glGenQueries(GLsizei(frame.queries.size()), frame.queries.data()));
glsafe(::glQueryCounter(frame.queries[0], GL_TIMESTAMP));
frame.count = 0;
frame.averaged = m_averaging;
m_recording = &frame;
m_last_mark = std::chrono::steady_clock::now();
}
void FrameProfiler::mark(const char* name)
{
if (m_recording == nullptr || m_recording->count == MAX_SECTIONS)
return;
Frame& frame = *m_recording;
const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
glsafe(::glQueryCounter(frame.queries[2 * frame.count + 1], GL_TIMESTAMP));
// Else the GPU time would include the CPU time spent until the driver flushes on its own.
glsafe(::glFlush());
// Sent with the next section's commands, so the time the GPU waits for them is not billed to it.
if (frame.count + 1 < MAX_SECTIONS)
glsafe(::glQueryCounter(frame.queries[2 * frame.count + 2], GL_TIMESTAMP));
frame.names[frame.count] = name;
frame.cpu_ms[frame.count] = std::chrono::duration<double, std::milli>(now - m_last_mark).count();
++frame.count;
m_last_mark = now;
}
void FrameProfiler::end_frame()
{
if (m_recording == nullptr)
return;
m_recording->pending = m_recording->count > 0;
m_recording = nullptr;
m_next = (m_next + 1) % FRAMES_IN_FLIGHT;
}
void FrameProfiler::collect(bool wait)
{
constexpr double SMOOTHING = 0.1;
// Oldest first: a frame whose results are not in yet holds back the newer ones too.
for (size_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
Frame& frame = m_frames[(m_next + i) % FRAMES_IN_FLIGHT];
if (!frame.pending)
continue;
if (!wait) {
GLint available = 0;
glsafe(::glGetQueryObjectiv(frame.queries[2 * frame.count - 1], GL_QUERY_RESULT_AVAILABLE, &available));
if (available == 0)
break;
}
std::array<GLuint64, 2 * MAX_SECTIONS> stamps{};
for (size_t j = 0; j < 2 * frame.count; ++j)
glsafe(::glGetQueryObjectui64v(frame.queries[j], GL_QUERY_RESULT, &stamps[j]));
frame.pending = false;
const bool averaged = frame.averaged && m_averaging;
if (averaged)
++m_averaged_frames;
std::vector<Section> sections;
sections.reserve(frame.count);
for (size_t j = 0; j < frame.count; ++j) {
Section section{ frame.names[j], frame.cpu_ms[j], stamps[2 * j + 1] > stamps[2 * j] ? double(stamps[2 * j + 1] - stamps[2 * j]) * 1e-6 : 0.0 };
if (averaged) {
auto sum = std::find_if(m_sums.begin(), m_sums.end(), [&section](const Section& s) { return std::strcmp(s.name, section.name) == 0; });
if (sum == m_sums.end())
m_sums.push_back(section);
else {
sum->cpu_ms += section.cpu_ms;
sum->gpu_ms += section.gpu_ms;
}
}
for (const Section& prev : m_sections) {
if (std::strcmp(prev.name, section.name) == 0) {
section.cpu_ms = prev.cpu_ms + SMOOTHING * (section.cpu_ms - prev.cpu_ms);
section.gpu_ms = prev.gpu_ms + SMOOTHING * (section.gpu_ms - prev.gpu_ms);
break;
}
}
sections.push_back(section);
}
m_sections = std::move(sections);
}
}
void FrameProfiler::start_averaging()
{
m_averaging = true;
m_sums.clear();
m_averaged_frames = 0;
}
std::vector<FrameProfiler::Section> FrameProfiler::finish_averaging(bool wait)
{
if (wait && glQueryCounter != nullptr)
collect(true);
std::vector<Section> averages = std::move(m_sums);
for (Section& section : averages) {
section.cpu_ms /= double(m_averaged_frames);
section.gpu_ms /= double(m_averaged_frames);
}
m_averaging = false;
m_sums.clear();
m_averaged_frames = 0;
return averages;
}
void FrameProfiler::reset()
{
for (Frame& frame : m_frames) {
if (frame.queries[0] != 0)
glsafe(::glDeleteQueries(GLsizei(frame.queries.size()), frame.queries.data()));
frame = Frame();
}
m_recording = nullptr;
m_sections.clear();
m_averaging = false;
m_sums.clear();
m_averaged_frames = 0;
}
} // namespace GUI
} // namespace Slic3r
+66
View File
@@ -0,0 +1,66 @@
#pragma once
#include <array>
#include <chrono>
#include <cstddef>
#include <vector>
namespace Slic3r {
namespace GUI {
// CPU and GPU time spent in the named sections of a frame, shown under the FPS overlay and averaged
// by the scene benchmark. A section runs from the previous mark to the one naming it; its GPU time
// leaves out any wait for the CPU to send its commands. GPU times are read back a few frames late,
// so profiling never waits on the GPU.
class FrameProfiler
{
public:
struct Section
{
const char* name{ nullptr };
double cpu_ms{ 0.0 };
double gpu_ms{ 0.0 };
};
void begin_frame();
void mark(const char* name);
void end_frame();
// The sections of the last frame read back, smoothed over the previous ones.
const std::vector<Section>& sections() const { return m_sections; }
// Averages the sections of the frames begun from now on, until finish_averaging().
void start_averaging();
bool is_averaging() const { return m_averaging; }
// With wait, first reads back the averaged frames still on the GPU, with the context current.
std::vector<Section> finish_averaging(bool wait);
// Frees the queries, with the context current.
void reset();
private:
static constexpr size_t FRAMES_IN_FLIGHT = 4;
static constexpr size_t MAX_SECTIONS = 32;
struct Frame
{
// A begin and an end timestamp per section.
std::array<unsigned int, 2 * MAX_SECTIONS> queries{};
std::array<const char*, MAX_SECTIONS> names{};
std::array<double, MAX_SECTIONS> cpu_ms{};
size_t count{ 0 };
bool pending{ false };
bool averaged{ false };
};
void collect(bool wait);
std::array<Frame, FRAMES_IN_FLIGHT> m_frames;
Frame* m_recording{ nullptr };
size_t m_next{ 0 };
std::chrono::steady_clock::time_point m_last_mark;
std::vector<Section> m_sections;
bool m_averaging{ false };
std::vector<Section> m_sums;
size_t m_averaged_frames{ 0 };
};
} // namespace GUI
} // namespace Slic3r
+29 -2
View File
@@ -80,6 +80,7 @@
#include <imgui/imgui_internal.h>
#include <glad/gl.h>
#include <boost/functional/hash.hpp>
#include <boost/log/trivial.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/nowide/cstdio.hpp>
@@ -1759,6 +1760,26 @@ void GCodeViewer::render_scene(int canvas_width, int canvas_height)
m_sequential_view.render_marker(!m_no_render_path, canvas_width, sequential_view_height(canvas_height), m_viewer.get_view_type());
}
size_t GCodeViewer::shadow_casters_signature() const
{
size_t hash = m_viewer.get_vertices_count();
const libvgcode::Interval& visible = m_viewer.get_view_visible_range();
const libvgcode::Interval& layers = m_viewer.get_layers_view_range();
for (size_t value : { size_t(visible[0]), size_t(visible[1]), size_t(layers[0]), size_t(layers[1]) })
boost::hash_combine(hash, value);
for (double value : { m_paths_bounding_box.min.x(), m_paths_bounding_box.min.y(), m_paths_bounding_box.min.z(),
m_paths_bounding_box.max.x(), m_paths_bounding_box.max.y(), m_paths_bounding_box.max.z() })
boost::hash_combine(hash, value);
boost::hash_combine(hash, m_viewer.is_top_layer_only_view_range());
for (size_t i = 0; i < libvgcode::GCODE_EXTRUSION_ROLES_COUNT; ++i)
boost::hash_combine(hash, m_viewer.is_extrusion_role_visible(libvgcode::EGCodeExtrusionRole(i)));
boost::hash_combine(hash, m_viewer.is_option_visible(libvgcode::EOptionType::Travels));
boost::hash_combine(hash, m_viewer.is_option_visible(libvgcode::EOptionType::Wipes));
for (float value : m_clipping_plane)
boost::hash_combine(hash, value);
return hash;
}
void GCodeViewer::render_shadow_casters(const Transform3d& light_view_matrix, const Transform3d& light_projection_matrix, const Vec3d& light_position)
{
if (!has_data())
@@ -1777,6 +1798,11 @@ void GCodeViewer::set_shadow_map(int texture_unit, const Transform3d& light_view
intensity, texel_size);
}
void GCodeViewer::set_light_top_dir(const Vec3d& direction)
{
m_viewer.set_light_top_dir(libvgcode::convert(static_cast<Vec3f>(direction.cast<float>())));
}
void GCodeViewer::set_tone(float exposure, float saturation)
{
m_viewer.set_tone(exposure, saturation);
@@ -1786,9 +1812,10 @@ void GCodeViewer::set_clipping_plane(const ClippingPlane& plane)
{
// Flipped to match ClippingPlane::distance().
const Vec3f normal = -plane.get_normal().cast<float>();
m_viewer.set_clipping_plane(plane.is_active() ?
m_clipping_plane = plane.is_active() ?
std::array<float, 4>{ normal.x(), normal.y(), normal.z(), float(plane.get_offset()) } :
std::array<float, 4>{ 0.0f, 0.0f, 0.0f, 1.0f });
std::array<float, 4>{ 0.0f, 0.0f, 0.0f, 1.0f };
m_viewer.set_clipping_plane(m_clipping_plane);
}
void GCodeViewer::render_overlay(int canvas_width, int canvas_height, int right_margin)
+7
View File
@@ -260,6 +260,8 @@ mutable bool m_no_render_path { false };
bool m_is_dark = false;
libvgcode::Viewer m_viewer;
// ORCA: section view, as the viewer has it. What it cuts away casts no shadow.
std::array<float, 4> m_clipping_plane{ 0.0f, 0.0f, 0.0f, 1.0f };
bool m_loaded_as_preview{ false };
public:
@@ -302,6 +304,7 @@ public:
void set_tone(float exposure, float saturation);
// ORCA: section view
void set_clipping_plane(const ClippingPlane& plane);
void set_light_top_dir(const Vec3d& direction);
//BBS
// void _render_calibration_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
// void _render_calibration_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager);
@@ -324,6 +327,10 @@ public:
std::vector<float> get_layers_times() const { return m_viewer.get_layers_estimated_times(); }
const std::array<size_t,2> &get_layers_z_range() const { return m_viewer.get_layers_view_range(); }
size_t get_vertices_count() const { return m_viewer.get_vertices_count(); }
size_t get_layers_count() const { return m_viewer.get_layers_count(); }
// ORCA: realistic view. Changes whenever the toolpaths casting shadows do.
size_t shadow_casters_signature() const;
const SequentialView& get_sequential_view() const { return m_sequential_view; }
void update_sequential_view_current(unsigned int first, unsigned int last);
+223 -75
View File
@@ -1303,6 +1303,7 @@ GLCanvas3D::~GLCanvas3D()
{
if (_set_current()) {
m_scene_cache.reset();
m_frame_profiler.reset();
if (m_fxaa_texture_id != 0) {
glsafe(::glDeleteTextures(1, &m_fxaa_texture_id));
m_fxaa_texture_id = 0;
@@ -2166,7 +2167,7 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
wxGetApp().imgui()->new_frame();
if (m_picking_enabled) {
if (m_picking_enabled && !m_benchmarking) {
if (m_rectangle_selection.is_dragging())
// picking pass using rectangle selection
_rectangular_selection_picking_pass();
@@ -2191,22 +2192,30 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
// An overlay-only frame reuses the last scene pass. The overlay is rebuilt either way, and drawn
// below once it is known whether the frame differs from the one on screen.
const bool reuse_scene = !scene_dirty && _can_reuse_cached_scene(camera);
// Only frames that redraw the scene are profiled.
if (!reuse_scene && (_is_render_timings_enabled() || m_frame_profiler.is_averaging()))
m_frame_profiler.begin_frame();
Slic3r::ScopeGuard profiler_guard([this]() { m_frame_profiler.end_frame(); });
if (!reuse_scene) {
_render_scene(camera, cnv_size);
if (!overlay_tick)
m_render_stats.increment_scene_fps_counter();
}
if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview)
if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview) {
// BBS: GUI refactor: add canvas size as parameters
_render_gcode_overlay(cnv_size.get_width(), cnv_size.get_height());
m_frame_profiler.mark("legend");
}
// draw overlays
_render_overlays();
m_frame_profiler.mark("ui");
const int current_fps = m_render_stats.get_fps_and_reset_if_needed();
if (_is_fps_overlay_enabled()) {
if (_is_fps_overlay_enabled() || _is_render_timings_enabled())
_render_fps_overlay(current_fps);
if (_is_fps_overlay_enabled()) {
// The timer requests an overlay-only frame a second from now. A frame it requested
// re-arms it only while a count is above zero.
if (!overlay_tick || current_fps > 0 || m_render_stats.get_scene_fps() > 0)
@@ -2296,12 +2305,14 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
wxGetApp().plater()->get_notification_manager()->render_notifications(*this, get_overlay_window_width(), bottom_margin, right_margin);
wxGetApp().plater()->get_dailytips()->render();
}
m_frame_profiler.mark("notifications");
ImDrawData* draw_data = wxGetApp().imgui()->end_frame();
std::optional<size_t> signature;
if (_is_frame_skipping_enabled())
signature = _overlay_signature(draw_data);
m_frame_profiler.mark("imgui end");
if (reuse_scene) {
// A reused scene under an unchanged overlay is the frame already on screen.
@@ -2311,13 +2322,16 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
}
_render_overlay_toolbars();
m_frame_profiler.mark("toolbars");
wxGetApp().imgui()->render(draw_data);
m_frame_profiler.mark("imgui draw");
// On Wayland, eglSwapBuffers blocks when the canvas is hidden or
// occluded. Skip the swap to avoid stalling the render loop.
if (m_canvas->IsShownOnScreen()) {
m_canvas->SwapBuffers();
m_frame_profiler.mark("swap");
if (!overlay_tick)
m_render_stats.increment_fps_counter();
m_presented_signature = signature;
@@ -2362,20 +2376,25 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
// floating would read as a rendering fault rather than a deliberate view option.
if (show_bed)
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes);
m_frame_profiler.mark("bed");
if (show_bed) //BBS: add outline logic
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid);
if (m_axes_at_bed_center && show_bed)
// Design tab: replace the plate's corner-origin grid with the origin-centred CAD grid.
_render_cad_grid(camera.get_view_matrix(), camera.get_projection_matrix());
m_frame_profiler.mark("plates");
//BBS: add outline logic
// Depth pass for object-on-object and self shadows; consumed by the gouraud shader below.
_render_shadows(camera.get_view_matrix(), camera.get_projection_matrix());
m_frame_profiler.mark("shadows");
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
m_frame_profiler.mark("objects");
_render_sla_slices();
_render_selection();
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
_render_wireframe_overlay();
m_frame_profiler.mark("transparent");
}
/* preview render */
else if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview) {
@@ -2384,12 +2403,17 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
_render_sla_slices();
_render_selection();
m_frame_profiler.mark("objects");
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes);
m_frame_profiler.mark("bed");
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, true, hover_id);
m_frame_profiler.mark("plates");
// Realistic view: the print casts a shadow onto the plate here as it does in View3D.
_render_shadows(camera.get_view_matrix(), camera.get_projection_matrix());
m_frame_profiler.mark("shadows");
// BBS: GUI refactor: add canvas size as parameters
_render_gcode(cnv_size.get_width(), cnv_size.get_height());
m_frame_profiler.mark("gcode");
}
/* assemble render*/
else if (m_canvas_type == ECanvasType::CanvasAssembleView) {
@@ -2406,6 +2430,7 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
// BBS: add outline logic
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
_render_wireframe_overlay();
m_frame_profiler.mark("objects");
}
_render_sequential_clearance();
@@ -2428,12 +2453,17 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
if (m_picking_enabled && m_rectangle_selection.is_dragging())
m_rectangle_selection.render(*this);
m_frame_profiler.mark("gizmos");
if (_is_ssao_enabled())
if (_is_ssao_enabled()) {
_render_ssao_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
m_frame_profiler.mark("ssao");
}
if (_is_fxaa_enabled())
if (_is_fxaa_enabled()) {
_render_fxaa_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
m_frame_profiler.mark("fxaa");
}
// Design tab: interactive 2D sketch overlay, drawn over the scene but
// beneath the UI overlays (toolbars, labels).
@@ -2443,6 +2473,7 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
#endif
_capture_scene_cache(camera);
m_frame_profiler.mark("cache");
}
void GLCanvas3D::render_thumbnail(ThumbnailData & thumbnail_data,
@@ -3435,7 +3466,7 @@ void GLCanvas3D::unbind_event_handlers()
void GLCanvas3D::on_idle(wxIdleEvent& evt)
{
if (!m_initialized)
if (!m_initialized || m_benchmarking)
return;
// Toolbar states, notifications and ImGui's own layout settling only touch the overlay.
@@ -7873,7 +7904,51 @@ int GLCanvas3D::_get_effective_fps_cap() const
bool GLCanvas3D::_is_fps_overlay_enabled() const
{
return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY);
return !m_benchmarking && wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY);
}
// Above the front left of the plate.
static const Vec3d STATIC_LIGHT_DIR = Vec3d(-0.4, -0.6, 1.0).normalized();
std::optional<Vec3d> GLCanvas3D::_static_light_dir_eye() const
{
if (!_is_realistic_view_enabled() || _shadow_mode() != EShadowMode::Static)
return std::nullopt;
const Matrix3d view_rot = wxGetApp().plater()->get_camera().get_view_matrix().matrix().block<3, 3>(0, 0);
return Vec3d((view_rot * STATIC_LIGHT_DIR).normalized());
}
GLCanvas3D::EShadowMode GLCanvas3D::_shadow_mode() const
{
const std::string mode = wxGetApp().app_config != nullptr ? wxGetApp().app_config->get(SETTING_OPENGL_REALISTIC_SHADOWS) : std::string();
return mode == "static" ? EShadowMode::Static : mode == "orbit" ? EShadowMode::Orbit : EShadowMode::Off;
}
size_t GLCanvas3D::_shadow_casters_signature(bool toolpath_casters) const
{
if (toolpath_casters)
return m_gcode_viewer.shadow_casters_signature();
size_t hash = 1;
for (const GLVolume* volume : m_volumes.volumes) {
if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower)
continue;
boost::hash_combine(hash, volume);
boost::hash_combine(hash, volume->model.vertices_count());
boost::hash_combine(hash, volume->model.indices_count());
const BoundingBoxf3& bb = volume->model.get_bounding_box();
for (double value : { bb.min.x(), bb.min.y(), bb.min.z(), bb.max.x(), bb.max.y(), bb.max.z() })
boost::hash_combine(hash, value);
const Transform3d world = volume->world_matrix();
for (int i = 0; i < 16; ++i)
boost::hash_combine(hash, world.matrix().data()[i]);
}
return hash;
}
bool GLCanvas3D::_is_render_timings_enabled() const
{
return !m_benchmarking && wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_SHOW_RENDER_TIMINGS);
}
bool GLCanvas3D::_is_scene_cache_enabled() const
@@ -7908,14 +7983,22 @@ bool GLCanvas3D::_is_frame_skipping_enabled() const
void GLCanvas3D::_render_fps_overlay(int fps) const
{
if (fps < 0)
const bool show_fps = _is_fps_overlay_enabled() && fps >= 0;
const std::vector<FrameProfiler::Section>& sections = m_frame_profiler.sections();
const bool show_timings = _is_render_timings_enabled() && !sections.empty();
if (!show_fps && !show_timings)
return;
ImGuiWrapper& imgui = *wxGetApp().imgui();
const float margin = 10.0f * get_scale();
const ImVec2 display_size = ImGui::GetIO().DisplaySize;
ImGui::SetNextWindowPos(ImVec2(display_size.x - margin, margin), ImGuiCond_Always, ImVec2(1.0f, 0.0f));
ImVec2 pos(display_size.x - margin, margin);
// The Preview legend takes the top-right corner.
if (const ImGuiWindow* legend = ImGui::FindWindowByName("Legend"); m_canvas_type == ECanvasType::CanvasPreview && legend != nullptr && legend->Active)
pos = ImVec2(legend->Pos.x - margin, legend->Pos.y);
ImGui::SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(1.0f, 0.0f));
ImGui::SetNextWindowBgAlpha(0.35f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f * get_scale());
imgui.begin(
std::string("###fps_overlay"),
ImGuiWindowFlags_AlwaysAutoResize |
@@ -7925,10 +8008,38 @@ void GLCanvas3D::_render_fps_overlay(int fps) const
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoInputs);
imgui.text(std::string("FPS: ") + std::to_string(fps));
// The subset of those frames that redrew the scene rather than reusing the cached one.
imgui.text(std::string("3D: ") + std::to_string(m_render_stats.get_scene_fps()));
if (show_fps) {
imgui.text(std::string("FPS: ") + std::to_string(fps));
ImGui::SameLine();
// The subset of those frames that redrew the scene rather than reusing the cached one.
imgui.text(std::string("3D: ") + std::to_string(m_render_stats.get_scene_fps()));
}
if (show_timings && ImGui::BeginTable("frame_sections", 3, ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableSetupColumn("ms");
ImGui::TableSetupColumn("CPU");
ImGui::TableSetupColumn("GPU");
ImGui::TableHeadersRow();
double cpu_ms = 0.0;
double gpu_ms = 0.0;
auto row = [](const char* name, double cpu, double gpu) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted(name);
ImGui::TableSetColumnIndex(1);
ImGui::Text("%.2f", cpu);
ImGui::TableSetColumnIndex(2);
ImGui::Text("%.2f", gpu);
};
for (const FrameProfiler::Section& section : sections) {
row(section.name, section.cpu_ms, section.gpu_ms);
cpu_ms += section.cpu_ms;
gpu_ms += section.gpu_ms;
}
row("total", cpu_ms, gpu_ms);
ImGui::EndTable();
}
imgui.end();
ImGui::PopStyleVar();
}
void GLCanvas3D::_render_fxaa_pass(unsigned int width, unsigned int height)
@@ -8278,7 +8389,8 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
return;
if (!_is_realistic_view_enabled())
return;
if (!wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS))
const EShadowMode mode = _shadow_mode();
if (mode == EShadowMode::Off)
return;
// The preview canvas holds no volumes of its own for FFF. Once slicing has run its printed
@@ -8293,10 +8405,11 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
if (OpenGLManager::get_framebuffers_type() == OpenGLManager::EFramebufferType::Arb) {
// Light direction (same as used in shading and plate shading)
// Orbit: the light used for shading, fixed to the camera. Static: a world light, also used for shading.
const Vec3d light_dir_eye = Vec3d(-0.4574957, 0.4574957, 0.7624929).normalized();
const Matrix3d view_rot = view_matrix.matrix().block<3, 3>(0, 0);
const Vec3d dir_to_light = (view_rot.transpose() * light_dir_eye).normalized();
const Vec3d dir_to_light = mode == EShadowMode::Static ? STATIC_LIGHT_DIR :
Vec3d((view_rot.transpose() * light_dir_eye).normalized());
// Bounding box of the printable objects (the shadow casters).
BoundingBoxf3 obj_bb;
@@ -8348,11 +8461,19 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
lmin = lmin.cwiseMin(lp);
lmax = lmax.cwiseMax(lp);
};
// The plate area a shadow can reach: the casters' footprint and where their corners land on z = 0.
Vec2d reach_min(DBL_MAX, DBL_MAX);
Vec2d reach_max(-DBL_MAX, -DBL_MAX);
auto reach = [&](const Vec3d& p) {
reach_min = reach_min.cwiseMin(Vec2d(p.x(), p.y()));
reach_max = reach_max.cwiseMax(Vec2d(p.x(), p.y()));
};
for (int i = 0; i < 8; ++i) {
const Vec3d corner((i & 1) ? obj_bb.max.x() : obj_bb.min.x(),
(i & 2) ? obj_bb.max.y() : obj_bb.min.y(),
(i & 4) ? obj_bb.max.z() : obj_bb.min.z());
enclose(corner);
reach(corner);
// Where this corner's shadow lands on z = 0, clamped to the plate so a grazing angle
// (t -> infinity) stays bounded.
if (ray_dir.z() < -1e-6) {
@@ -8362,6 +8483,7 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
s.y() = std::min(std::max(s.y(), plate_bb.min.y()), plate_bb.max.y());
s.z() = 0.0;
enclose(s);
reach(s);
}
}
@@ -8395,6 +8517,11 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
// Create / resize the depth texture and FBO
const unsigned int size = 2048;
// Padded by the filter's 2 texel reach, stretched where the light grazes the plate.
const double reach_margin = 3.0 * 2.0 * std::max(halfx, halfy) / size / std::max(0.1, std::abs(dir_to_light.z()));
m_shadow_plate_bounds = { float(reach_min.x() - reach_margin), float(reach_min.y() - reach_margin),
float(reach_max.x() + reach_margin), float(reach_max.y() + reach_margin) };
if (m_shadow_map_texture_id == 0) {
glsafe(::glGenTextures(1, &m_shadow_map_texture_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_shadow_map_texture_id));
@@ -8414,66 +8541,80 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
if (m_shadow_map_fbo == 0)
glsafe(::glGenFramebuffers(1, &m_shadow_map_fbo));
// Save OpenGL state that we will modify
GLint prev_viewport[4] = { 0, 0, 0, 0 };
glsafe(::glGetIntegerv(GL_VIEWPORT, prev_viewport));
GLint prev_fbo = 0;
glsafe(::glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo));
GLboolean prev_color_mask[4] = { GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE };
glsafe(::glGetBooleanv(GL_COLOR_WRITEMASK, prev_color_mask));
const GLboolean prev_cull = ::glIsEnabled(GL_CULL_FACE);
GLint prev_depth_func = GL_LESS;
glsafe(::glGetIntegerv(GL_DEPTH_FUNC, &prev_depth_func));
GLboolean prev_depth_mask = GL_TRUE;
glsafe(::glGetBooleanv(GL_DEPTH_WRITEMASK, &prev_depth_mask));
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, m_shadow_map_fbo));
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadow_map_texture_id, 0));
glsafe(::glDrawBuffer(GL_NONE));
glsafe(::glReadBuffer(GL_NONE));
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(prev_fbo)));
m_shadow_map_valid = false;
} else {
glsafe(::glViewport(0, 0, size, size));
glsafe(::glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE));
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glDepthMask(GL_TRUE));
glsafe(::glDepthFunc(GL_LESS));
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
glsafe(::glPolygonOffset(4.0f, 4.0f));
glsafe(::glDisable(GL_CULL_FACE));
if (toolpath_casters)
m_gcode_viewer.render_shadow_casters(Transform3d(light_view), Transform3d(light_proj), eye);
// Only this branch draws through "flat"; the toolpaths bring their own program.
else if (GLShaderProgram* shader = wxGetApp().get_shader("flat"); shader != nullptr) {
shader->start_using();
shader->set_uniform("projection_matrix", Transform3d(light_proj));
for (GLVolume* volume : m_volumes.volumes) {
if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower)
continue;
const Transform3d view_model = Transform3d(light_view) * volume->world_matrix();
shader->set_uniform("view_model_matrix", view_model);
volume->model.render(shader);
}
shader->stop_using();
}
// Restore state
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
glsafe(::glColorMask(prev_color_mask[0], prev_color_mask[1], prev_color_mask[2], prev_color_mask[3]));
if (prev_cull)
glsafe(::glEnable(GL_CULL_FACE));
else
glsafe(::glDisable(GL_CULL_FACE));
glsafe(::glDepthFunc(prev_depth_func));
glsafe(::glDepthMask(prev_depth_mask));
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(prev_fbo)));
glsafe(::glViewport(prev_viewport[0], prev_viewport[1], prev_viewport[2], prev_viewport[3]));
// A static light keeps the map until its frustum or the casters change.
size_t cache_key = 0;
if (mode == EShadowMode::Static) {
cache_key = _shadow_casters_signature(toolpath_casters);
for (int i = 0; i < 16; ++i)
boost::hash_combine(cache_key, m_shadow_light_vp.matrix().data()[i]);
}
if (cache_key != 0 && cache_key == m_shadow_map_key)
m_shadow_map_valid = true;
else {
m_shadow_map_key = 0;
// Save OpenGL state that we will modify
GLint prev_viewport[4] = { 0, 0, 0, 0 };
glsafe(::glGetIntegerv(GL_VIEWPORT, prev_viewport));
GLint prev_fbo = 0;
glsafe(::glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo));
GLboolean prev_color_mask[4] = { GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE };
glsafe(::glGetBooleanv(GL_COLOR_WRITEMASK, prev_color_mask));
const GLboolean prev_cull = ::glIsEnabled(GL_CULL_FACE);
GLint prev_depth_func = GL_LESS;
glsafe(::glGetIntegerv(GL_DEPTH_FUNC, &prev_depth_func));
GLboolean prev_depth_mask = GL_TRUE;
glsafe(::glGetBooleanv(GL_DEPTH_WRITEMASK, &prev_depth_mask));
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, m_shadow_map_fbo));
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadow_map_texture_id, 0));
glsafe(::glDrawBuffer(GL_NONE));
glsafe(::glReadBuffer(GL_NONE));
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(prev_fbo)));
m_shadow_map_valid = false;
} else {
glsafe(::glViewport(0, 0, size, size));
glsafe(::glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE));
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glDepthMask(GL_TRUE));
glsafe(::glDepthFunc(GL_LESS));
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
glsafe(::glPolygonOffset(4.0f, 4.0f));
glsafe(::glDisable(GL_CULL_FACE));
if (toolpath_casters)
m_gcode_viewer.render_shadow_casters(Transform3d(light_view), Transform3d(light_proj), eye);
// Only this branch draws through "flat"; the toolpaths bring their own program.
else if (GLShaderProgram* shader = wxGetApp().get_shader("flat"); shader != nullptr) {
shader->start_using();
shader->set_uniform("projection_matrix", Transform3d(light_proj));
for (GLVolume* volume : m_volumes.volumes) {
if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower)
continue;
const Transform3d view_model = Transform3d(light_view) * volume->world_matrix();
shader->set_uniform("view_model_matrix", view_model);
volume->model.render(shader);
}
shader->stop_using();
}
// Restore state
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
glsafe(::glColorMask(prev_color_mask[0], prev_color_mask[1], prev_color_mask[2], prev_color_mask[3]));
if (prev_cull)
glsafe(::glEnable(GL_CULL_FACE));
else
glsafe(::glDisable(GL_CULL_FACE));
glsafe(::glDepthFunc(prev_depth_func));
glsafe(::glDepthMask(prev_depth_mask));
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(prev_fbo)));
glsafe(::glViewport(prev_viewport[0], prev_viewport[1], prev_viewport[2], prev_viewport[3]));
m_shadow_map_valid = true;
}
if (m_shadow_map_valid)
m_shadow_map_key = cache_key;
}
} else {
m_shadow_map_valid = false;
@@ -8573,6 +8714,7 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform
plate_shader->set_uniform("shadow_light_vp", m_shadow_light_vp);
plate_shader->set_uniform("shadow_intensity", 0.35f);
plate_shader->set_uniform("shadow_map_texel", 1.0f / static_cast<float>(m_shadow_map_size));
plate_shader->set_uniform("shadow_bounds", m_shadow_plate_bounds);
m_plate_shadow_mask.render(plate_shader);
plate_shader->stop_using();
@@ -8697,6 +8839,10 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
}
else
shader->set_uniform("shadow_intensity", 0.0f);
const std::optional<Vec3d> static_light = _static_light_dir_eye();
shader->set_uniform("use_static_light", static_light.has_value());
if (static_light.has_value())
shader->set_uniform("static_light_dir", *static_light);
const Size& cvn_size = get_canvas_size();
{
@@ -8789,6 +8935,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
glsafe(::glActiveTexture(GL_TEXTURE0));
}
shader->set_uniform("use_static_light", false);
shader->stop_using();
}
@@ -8938,8 +9085,9 @@ void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height)
// again - realistic view with at least one lossy pass on - else the lift would just clip.
const AppConfig* cfg = wxGetApp().app_config;
const bool lossy_passes = cfg != nullptr && _is_realistic_view_enabled() &&
(cfg->get_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS) || cfg->get_bool(SETTING_OPENGL_PHONG_SSAO));
(_shadow_mode() != EShadowMode::Off || cfg->get_bool(SETTING_OPENGL_PHONG_SSAO));
m_gcode_viewer.set_tone(lossy_passes ? 1.1f : 1.0f, 1.15f);
m_gcode_viewer.set_light_top_dir(_static_light_dir_eye().value_or(Vec3d(-0.4574957, 0.4574957, 0.7624929)));
m_gcode_viewer.render_scene(canvas_width, canvas_height);
+17
View File
@@ -37,6 +37,7 @@
#include "Camera.hpp"
#include "SceneRaycaster.hpp"
#include "SceneCache.hpp"
#include "FrameProfiler.hpp"
#include "IMToolbar.hpp"
#include "slic3r/GUI/3DBed.hpp"
#include "libslic3r/Slicing.hpp"
@@ -714,6 +715,8 @@ private:
bool m_reload_delayed;
RenderStats m_render_stats;
FrameProfiler m_frame_profiler;
bool m_benchmarking{ false };
std::chrono::time_point<std::chrono::steady_clock> m_last_frame_start_time{ std::chrono::steady_clock::now() };
int m_imgui_undo_redo_hovered_pos{ -1 };
@@ -846,6 +849,10 @@ public:
unsigned int m_shadow_map_size{ 0 };
Transform3d m_shadow_light_vp{ Transform3d::Identity() };
bool m_shadow_map_valid{ false };
// Casters and light frustum the map was last rendered for, under a static light. 0 when none.
size_t m_shadow_map_key{ 0 };
// Plate rectangle a shadow can reach, min xy then max xy.
std::array<float, 4> m_shadow_plate_bounds{ { 0.0f, 0.0f, 0.0f, 0.0f } };
public:
explicit GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed);
~GLCanvas3D();
@@ -1272,6 +1279,10 @@ public:
void schedule_extra_frame(int milliseconds);
// The scene benchmark draws every frame itself, without picking or the FPS and timings overlays.
void set_benchmarking(bool benchmarking) { m_benchmarking = benchmarking; }
FrameProfiler& get_frame_profiler() { return m_frame_profiler; }
int get_main_toolbar_item_id(const std::string& name) const { return m_main_toolbar.get_item_id(name); }
void force_main_toolbar_left_action(int item_id) { m_main_toolbar.force_left_action(item_id, *this); }
void force_main_toolbar_right_action(int item_id) { m_main_toolbar.force_right_action(item_id, *this); }
@@ -1385,6 +1396,12 @@ private:
bool _is_ssao_enabled() const;
int _get_effective_fps_cap() const;
bool _is_fps_overlay_enabled() const;
bool _is_render_timings_enabled() const;
enum class EShadowMode { Off, Static, Orbit };
EShadowMode _shadow_mode() const;
// Direction to the static shadow light in eye space, when it lights the scene.
std::optional<Vec3d> _static_light_dir_eye() const;
size_t _shadow_casters_signature(bool toolpath_casters) const;
bool _is_scene_cache_enabled() const;
bool _is_scene_cacheable() const;
bool _is_frame_skipping_enabled() const;
+2 -2
View File
@@ -8631,8 +8631,8 @@ void GUI_App::open_preferences(PreferencesTab tab, const std::string& highlight_
{
// Render settings the canvas reads every frame; a change needs one redraw to show.
static constexpr const char* opengl_render_setting_keys[] = {
SETTING_OPENGL_FXAA_ENABLED, SETTING_OPENGL_FPS_CAP, SETTING_OPENGL_SHOW_FPS_OVERLAY, SETTING_OPENGL_SCENE_CACHE,
SETTING_OPENGL_SKIP_IDENTICAL_FRAMES
SETTING_OPENGL_FXAA_ENABLED, SETTING_OPENGL_FPS_CAP, SETTING_OPENGL_SHOW_FPS_OVERLAY, SETTING_OPENGL_SHOW_RENDER_TIMINGS,
SETTING_OPENGL_SCENE_CACHE, SETTING_OPENGL_SKIP_IDENTICAL_FRAMES, SETTING_OPENGL_REALISTIC_SHADOWS
};
std::vector<std::string> previous_opengl_render_settings;
for (const char* key : opengl_render_setting_keys)
+5
View File
@@ -117,6 +117,7 @@
#include "NotificationManager.hpp"
#include "MarkdownTip.hpp"
#include "NetworkTestDialog.hpp"
#include "SceneBenchmark.hpp"
#include "ConfigWizard.hpp"
#include "Widgets/WebView.hpp"
#include "DailyTips.hpp"
@@ -2786,6 +2787,10 @@ wxMenu* MainFrame::generate_help_menu()
dlg.ShowModal();
});
if (wxGetApp().is_editor())
append_menu_item(helpMenu, wxID_ANY, _L("Benchmark 3D Scene"), _L("Measure how fast the 3D scene renders in Prepare and Preview"),
[](wxCommandEvent&) { run_scene_benchmark(); });
helpMenu->AppendSeparator();
append_menu_item(helpMenu, wxID_ANY, _L("Show Tip of the Day"), _L("Show Tip of the Day"), [](wxCommandEvent&) {
+5
View File
@@ -17,6 +17,7 @@
#include "Plater.hpp"
#include "PluginsDialog.hpp"
#include "PlateSettingsDialog.hpp"
#include "SceneBenchmark.hpp"
#include "DeviceCore/DevManager.h"
#include "libslic3r/Config.hpp"
@@ -591,6 +592,10 @@ std::vector<NativeCommand> build_command_catalog()
dlg.ShowModal();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("help_benchmark_3d_scene", _u8L("Benchmark 3D Scene"), _u8L("Help"), [](const std::string&) {
run_scene_benchmark();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add_with_icon("help_tip_of_the_day", _u8L("Show Tip of the Day"), _u8L("Help"), "info", [](const std::string&) {
if (Plater* plater = wxGetApp().plater()) {
plater->get_dailytips()->open();
+28 -4
View File
@@ -65,6 +65,7 @@
#include <wx/types.h>
#include <wx/timer.h>
#include "NetworkTestDialog.hpp"
#include "SceneBenchmark.hpp"
#include "Widgets/StaticLine.hpp"
#include "Widgets/RadioGroup.hpp"
#include "Shortcuts.hpp"
@@ -1999,11 +2000,14 @@ void PreferencesDialog::create_items()
);
g_sizer->Add(item_realistic_ssao);
auto item_realistic_shadows = create_item_checkbox(
std::vector<wxString> ShadowsLabels = { _L("Off"), _L("Static"), _L("Orbit") };
std::vector<std::string> ShadowsValues = { "off", "static", "orbit" };
auto item_realistic_shadows = create_item_combobox(
_L("Shadows"),
_L("Renders cast shadows on the plate, other objects, and each object onto itself in realistic view."),
SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS
);
_L("Renders cast shadows on the plate, other objects, and each object onto itself in realistic view.\n"
"Static: the light stays fixed in the world, so the shadows are only recomputed when the scene changes.\n"
"Orbit: the light turns with the camera, recomputing the shadows every frame the camera moves."),
SETTING_OPENGL_REALISTIC_SHADOWS, ShadowsLabels, ShadowsValues);
g_sizer->Add(item_realistic_shadows);
//// GRAPHICS > Anti-aliasing
@@ -2076,6 +2080,26 @@ void PreferencesDialog::create_items()
);
g_sizer->Add(item_fps_overlay);
auto item_render_timings = create_item_checkbox(
_L("Show render timings"),
_L("Displays how many milliseconds each part of a frame that redraws the 3D scene takes, in the top-right corner of the viewport.") + "\n" +
_L("CPU: time spent issuing the drawing commands.") + "\n" +
_L("GPU: time the graphics card spent running them.") + "\n" +
_L("Adds a small overhead to each frame while enabled."),
SETTING_OPENGL_SHOW_RENDER_TIMINGS
);
g_sizer->Add(item_render_timings);
if (wxGetApp().is_editor()) {
auto item_benchmark = create_item_button(_L("3D scene benchmark"), _L("Run") + " " + dots, "",
_L("Replaces the current project with the OrcaSliced Combo, then measures the frame rate and render timings while the camera turns around it in Prepare and Preview, and while the layer slider moves through the sliced layers."),
[this]() {
EndModal(wxID_OK);
wxGetApp().CallAfter([] { run_scene_benchmark(); });
});
g_sizer->Add(item_benchmark);
}
//// GRAPHICS > G-code Preview
g_sizer->Add(create_item_title(_L("G-code Preview")), 1, wxEXPAND);
+555
View File
@@ -0,0 +1,555 @@
#include "SceneBenchmark.hpp"
#include "3DScene.hpp"
#include "BuildCommit.hpp"
#include "Camera.hpp"
#include "GLCanvas3D.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "GUI_Factories.hpp"
#include "I18N.hpp"
#include "IMSlider.hpp"
#include "MainFrame.hpp"
#include "MsgDialog.hpp"
#include "NotificationManager.hpp"
#include "OpenGLManager.hpp"
#include "Plater.hpp"
#include "Widgets/DialogButtons.hpp"
#include "Widgets/Label.hpp"
#include "Widgets/ProgressBar.hpp"
#include "Widgets/StateColor.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/libslic3r.h"
#include <glad/gl.h>
#include <wx/clipbrd.h>
#include <wx/glcanvas.h>
#include <wx/textctrl.h>
#include <wx/timer.h>
#include <wx/utils.h>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstring>
#include <iomanip>
#include <memory>
#include <numeric>
#include <sstream>
namespace Slic3r {
namespace GUI {
FrameTimeStats frame_time_stats(std::vector<double> frame_ms)
{
FrameTimeStats stats;
if (frame_ms.empty())
return stats;
std::sort(frame_ms.begin(), frame_ms.end());
const double total_ms = std::accumulate(frame_ms.begin(), frame_ms.end(), 0.0);
auto percentile = [&frame_ms](double p) {
const size_t rank = size_t(std::ceil(p * double(frame_ms.size()) / 100.0));
return frame_ms[std::clamp<size_t>(rank, 1, frame_ms.size()) - 1];
};
stats.fps = total_ms > 0.0 ? 1000.0 * double(frame_ms.size()) / total_ms : 0.0;
stats.average_ms = total_ms / double(frame_ms.size());
stats.median_ms = percentile(50.0);
stats.p95_ms = percentile(95.0);
stats.p99_ms = percentile(99.0);
stats.max_ms = frame_ms.back();
return stats;
}
namespace {
constexpr size_t WARMUP_FRAMES = 30;
// A scene runs the camera path twice: timing the frames, then averaging the render timings.
constexpr size_t PASS_FRAMES = 360;
constexpr size_t SCENE_FRAMES = WARMUP_FRAMES + 2 * PASS_FRAMES;
struct SceneResult
{
std::string name;
std::vector<double> frame_ms;
std::vector<FrameProfiler::Section> sections;
};
class SceneBenchmarkDialog : public DPIDialog
{
public:
SceneBenchmarkDialog();
~SceneBenchmarkDialog() override;
bool is_running() const { return m_stage != Stage::Done; }
protected:
void on_dpi_changed(const wxRect& suggested_rect) override;
private:
enum class Stage { Loading, Prepare, Slicing, Preview, Layers, Done };
void on_timer(wxTimerEvent& evt);
void on_idle(wxIdleEvent& evt);
void on_first_frame();
void begin_scene(GLCanvas3D* canvas, Stage stage);
void end_scene();
void stop_scene();
void start_slicing();
void set_camera(size_t pass_frame);
void set_top_layer(size_t pass_frame);
void set_status(const wxString& status);
void finish(const wxString& error = wxString());
void show_report(const wxString& error);
std::string report() const;
Stage m_stage{ Stage::Loading };
std::unique_ptr<wxWindowDisabler> m_disabler;
wxTimer m_timer;
int m_polls{ 0 };
Label* m_status{ nullptr };
ProgressBar* m_progress{ nullptr };
GLCanvas3D* m_canvas{ nullptr };
Camera m_saved_camera;
Vec3d m_target{ Vec3d::Zero() };
double m_zoom{ 1.0 };
size_t m_frame{ 0 };
std::chrono::steady_clock::time_point m_last_frame;
// The layer slider's top before the Layers scene moved it, -1 outside that scene.
int m_saved_top_layer{ -1 };
int m_swap_interval{ wxGLCanvas::DefaultSwapInterval };
bool m_restore_swap_interval{ false };
std::vector<SceneResult> m_results;
// Read on the first frame, with the context current.
std::string m_vsync;
int m_msaa_samples{ 0 };
int m_viewport_width{ 0 };
int m_viewport_height{ 0 };
std::string m_camera_type;
};
SceneBenchmarkDialog* s_benchmark = nullptr;
SceneBenchmarkDialog::SceneBenchmarkDialog()
: DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, _L("Benchmark 3D Scene"), wxDefaultPosition, wxDefaultSize,
wxCAPTION | wxCLOSE_BOX)
, m_timer(this)
{
s_benchmark = this;
SetBackgroundColour(*wxWHITE);
SetFont(Label::Body_14);
auto* sizer = new wxBoxSizer(wxVERTICAL);
sizer->SetMinSize(wxSize(FromDIP(360), -1));
m_status = new Label(this, _L("Loading the benchmark model") + dots);
m_progress = new ProgressBar(this, wxID_ANY, 100);
m_progress->SetHeight(FromDIP(8));
m_progress->SetMaxSize(wxSize(-1, FromDIP(8)));
m_progress->SetProgressForedColour(StateColor::darkModeColorFor(wxColour("#DFDFDF")));
sizer->Add(m_status, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, FromDIP(15));
sizer->Add(m_progress, 0, wxEXPAND | wxALL, FromDIP(15));
auto* buttons = new DialogButtons(this, {"Cancel"});
buttons->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { Close(); });
sizer->Add(buttons, 0, wxEXPAND);
SetSizerAndFit(sizer);
wxGetApp().UpdateDlgDarkUI(this);
Bind(wxEVT_TIMER, &SceneBenchmarkDialog::on_timer, this);
Bind(wxEVT_IDLE, &SceneBenchmarkDialog::on_idle, this);
Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& evt) {
if (evt.GetKeyCode() == WXK_ESCAPE)
Close();
else
evt.Skip();
});
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent&) {
m_timer.Stop();
stop_scene();
m_disabler.reset();
Destroy();
});
// A corner of the 3D view, away from the model.
const wxRect view = wxGetApp().plater()->get_current_canvas3D()->get_wxglcanvas()->GetScreenRect();
SetPosition(wxPoint(view.GetLeft() + FromDIP(20), view.GetBottom() - GetSize().GetHeight() - FromDIP(20)));
m_disabler = std::make_unique<wxWindowDisabler>(this);
m_timer.Start(100);
}
SceneBenchmarkDialog::~SceneBenchmarkDialog()
{
// At shutdown the windows it disabled may be gone already.
if (wxGetApp().is_closing())
(void) m_disabler.release();
if (s_benchmark == this)
s_benchmark = nullptr;
}
void SceneBenchmarkDialog::on_dpi_changed(const wxRect&)
{
Fit();
Refresh();
}
void SceneBenchmarkDialog::on_timer(wxTimerEvent&)
{
Plater* plater = wxGetApp().plater();
if (wxGetApp().is_closing() || plater == nullptr) {
m_timer.Stop();
return;
}
if (m_stage == Stage::Loading) {
if (!plater->get_ui_job_worker().is_idle())
return;
m_timer.Stop();
GLCanvas3D* canvas = plater->get_view3D_canvas3D();
const BoundingBoxf3 box = canvas->volumes_bounding_box(true);
if (!box.defined) {
finish(_L("The benchmark model could not be loaded."));
return;
}
m_target = box.center();
const Size size = canvas->get_canvas_size();
m_zoom = double(std::min(size.get_width(), size.get_height())) / (1.1 * box.size().norm());
plater->deselect_all();
begin_scene(canvas, Stage::Prepare);
} else if (m_stage == Stage::Slicing) {
if (plater->is_background_process_slicing())
return;
GLCanvas3D* canvas = plater->get_preview_canvas3D();
if (canvas->get_gcode_viewer().has_data() && canvas->get_wxglcanvas()->IsShownOnScreen()) {
m_timer.Stop();
plater->get_notification_manager()->set_slicing_progress_hidden();
begin_scene(canvas, Stage::Preview);
} else if (++m_polls > 30)
finish(_L("Slicing failed, so only Prepare was measured."));
}
}
void SceneBenchmarkDialog::on_idle(wxIdleEvent& evt)
{
evt.Skip();
if (m_canvas == nullptr || wxGetApp().is_closing())
return;
if (!m_canvas->get_wxglcanvas()->IsShownOnScreen()) {
finish(_L("The benchmark stopped because the 3D view was hidden."));
return;
}
const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
if (m_frame > WARMUP_FRAMES && m_frame <= WARMUP_FRAMES + PASS_FRAMES)
m_results.back().frame_ms.push_back(std::chrono::duration<double, std::milli>(now - m_last_frame).count());
m_last_frame = now;
FrameProfiler& profiler = m_canvas->get_frame_profiler();
if (m_frame == WARMUP_FRAMES + PASS_FRAMES)
profiler.start_averaging();
if (m_stage == Stage::Layers)
// The warm-up ends where the path starts, so the slider moves in every frame.
set_top_layer(m_frame < WARMUP_FRAMES ? PASS_FRAMES - WARMUP_FRAMES + m_frame : (m_frame - WARMUP_FRAMES) % PASS_FRAMES);
else
set_camera(m_frame < WARMUP_FRAMES ? 0 : (m_frame - WARMUP_FRAMES) % PASS_FRAMES);
m_canvas->render();
if (m_frame == 0)
on_first_frame();
if (++m_frame == SCENE_FRAMES) {
m_results.back().sections = profiler.finish_averaging(true);
end_scene();
// The next scene starts right away, with no timer to bring the next idle event.
if (m_canvas != nullptr)
evt.RequestMore();
return;
}
const int scene = m_stage == Stage::Prepare ? 0 : m_stage == Stage::Preview ? 1 : 2;
if (m_frame % 30 == 0)
m_progress->SetValue(int(100 * (scene * SCENE_FRAMES + m_frame) / (3 * SCENE_FRAMES)));
evt.RequestMore();
}
void SceneBenchmarkDialog::on_first_frame()
{
// A benchmark measures the frames the GPU can draw, not the display refresh rate.
wxGLCanvas* glcanvas = m_canvas->get_wxglcanvas();
m_swap_interval = glcanvas->GetSwapInterval();
const bool known = m_swap_interval != wxGLCanvas::DefaultSwapInterval;
if (known && m_swap_interval != 0)
m_restore_swap_interval = glcanvas->SetSwapInterval(0) != wxGLCanvas::SwapInterval::NotSet;
if (m_results.size() > 1)
return;
m_vsync = !known ? "unknown" : m_swap_interval == 0 || m_restore_swap_interval ? "off" : "on";
GLint samples = 0;
glsafe(::glGetIntegerv(GL_SAMPLES, &samples));
m_msaa_samples = samples;
const Camera& camera = wxGetApp().plater()->get_camera();
const std::array<int, 4>& viewport = camera.get_viewport();
m_viewport_width = viewport[2];
m_viewport_height = viewport[3];
m_camera_type = camera.get_type_as_string();
}
void SceneBenchmarkDialog::begin_scene(GLCanvas3D* canvas, Stage stage)
{
m_stage = stage;
m_canvas = canvas;
m_saved_camera = wxGetApp().plater()->get_camera();
m_frame = 0;
m_restore_swap_interval = false;
m_results.push_back({stage == Stage::Prepare ? "Prepare" : stage == Stage::Preview ? "Preview" : "Layers", {}, {}});
m_results.back().frame_ms.reserve(PASS_FRAMES);
set_status(stage == Stage::Prepare ? _L("Turning the camera in Prepare") :
stage == Stage::Preview ? _L("Turning the camera in Preview") :
_L("Moving through the layers in Preview"));
if (stage == Stage::Layers) {
m_saved_top_layer = canvas->get_gcode_viewer().get_layers_slider()->GetHigherValue();
set_camera(0);
}
canvas->set_benchmarking(true);
}
void SceneBenchmarkDialog::end_scene()
{
const Stage stage = m_stage;
GLCanvas3D* canvas = m_canvas;
stop_scene();
if (stage == Stage::Prepare)
start_slicing();
else if (stage == Stage::Preview)
begin_scene(canvas, Stage::Layers);
else
finish();
}
void SceneBenchmarkDialog::stop_scene()
{
if (m_canvas == nullptr)
return;
if (m_frame < SCENE_FRAMES)
m_results.pop_back();
FrameProfiler& profiler = m_canvas->get_frame_profiler();
if (profiler.is_averaging())
profiler.finish_averaging(false);
if (m_restore_swap_interval && m_canvas->make_current_for_postinit())
m_canvas->get_wxglcanvas()->SetSwapInterval(m_swap_interval);
if (m_saved_top_layer >= 0) {
m_canvas->get_gcode_viewer().get_layers_slider()->SetHigherValue(m_saved_top_layer);
m_saved_top_layer = -1;
}
m_canvas->set_benchmarking(false);
m_canvas->set_as_dirty();
wxGetApp().plater()->get_camera() = m_saved_camera;
m_canvas = nullptr;
}
void SceneBenchmarkDialog::start_slicing()
{
m_stage = Stage::Slicing;
set_status(_L("Slicing"));
m_progress->SetValue(33);
Plater* plater = wxGetApp().plater();
plater->reslice();
plater->select_view_3D("Preview", false);
wxGetApp().mainframe->select_tab(TAB_ID_PREVIEW);
m_polls = 0;
m_timer.Start(100);
}
void SceneBenchmarkDialog::set_camera(size_t pass_frame)
{
// Two turns around the model while the view goes three times from below the plate to nearly
// straight above it, zooming in and out twice.
const double t = double(pass_frame) / double(PASS_FRAMES);
const double azimuth = (-0.75 + 4.0 * t) * PI;
const double elevation = (30.0 + 55.0 * std::sin(6.0 * PI * t)) * PI / 180.0;
const Vec3d dir(std::cos(elevation) * std::cos(azimuth), std::cos(elevation) * std::sin(azimuth), std::sin(elevation));
Camera& camera = wxGetApp().plater()->get_camera();
camera.look_at(m_target + Camera::DefaultDistance * dir, m_target, Vec3d::UnitZ());
camera.set_zoom(m_zoom * (1.0 + 0.4 * std::sin(4.0 * PI * t)));
}
void SceneBenchmarkDialog::set_top_layer(size_t pass_frame)
{
// Down to the first layer and back up, as dragging the top of the layer slider does.
IMSlider* slider = m_canvas->get_gcode_viewer().get_layers_slider();
const double t = double(pass_frame) / double(PASS_FRAMES);
const int span = slider->GetMaxValue() - slider->GetMinValue();
slider->SetHigherValue(slider->GetMinValue() + int(std::lround(span * std::abs(1.0 - 2.0 * t))));
}
void SceneBenchmarkDialog::set_status(const wxString& status)
{
m_status->SetLabel(status + dots);
Fit();
}
void SceneBenchmarkDialog::finish(const wxString& error)
{
m_timer.Stop();
stop_scene();
m_disabler.reset();
m_stage = Stage::Done;
show_report(error);
}
void SceneBenchmarkDialog::show_report(const wxString& error)
{
DestroyChildren();
m_status = nullptr;
m_progress = nullptr;
auto* sizer = new wxBoxSizer(wxVERTICAL);
if (!error.IsEmpty()) {
auto* message = new Label(this, error);
message->Wrap(FromDIP(500));
sizer->Add(message, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, FromDIP(15));
}
std::string text;
if (!m_results.empty()) {
text = report();
auto* content = new wxTextCtrl(this, wxID_ANY, from_u8(text), wxDefaultPosition, wxDefaultSize,
wxTE_MULTILINE | wxTE_READONLY | wxTE_DONTWRAP | wxBORDER_SIMPLE);
content->SetFont(wxGetApp().code_font());
wxClientDC dc(content);
dc.SetFont(content->GetFont());
const wxSize extent = dc.GetMultiLineTextExtent(content->GetValue()) + FromDIP(wxSize(40, 20));
content->SetMinSize(wxSize(extent.GetWidth(), std::min(extent.GetHeight(), FromDIP(560))));
sizer->Add(content, 1, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, FromDIP(15));
}
auto* buttons = new DialogButtons(this, text.empty() ? std::vector<wxString>{"OK"} : std::vector<wxString>{"Copy", "OK"});
if (Button* copy = buttons->GetButtonFromID(wxID_COPY)) {
copy->SetToolTip(_L("Copy the results to the clipboard"));
copy->Bind(wxEVT_BUTTON, [text](wxCommandEvent&) {
wxClipboardLocker lock;
if (!lock)
return;
wxTheClipboard->SetData(new wxTextDataObject(from_u8(text)));
});
}
buttons->GetOK()->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { Close(); });
sizer->Add(buttons, 0, wxEXPAND);
SetSizerAndFit(sizer);
CenterOnParent();
wxGetApp().UpdateDlgDarkUI(this);
Raise();
}
std::string SceneBenchmarkDialog::report() const
{
const AppConfig& config = *wxGetApp().app_config;
const OpenGLManager::GLInfo& gl = OpenGLManager::get_gl_info();
auto on_off = [](bool on) { return on ? "on" : "off"; };
std::ostringstream out;
out << SLIC3R_APP_NAME << " 3D scene benchmark\n"
<< "Version: " << SoftFever_VERSION << " (" << build_commit_label << ")\n"
<< "GPU: " << gl.get_renderer() << '\n'
<< "OpenGL: " << gl.get_version() << '\n'
<< "Viewport: " << m_viewport_width << " x " << m_viewport_height << " px, " << m_camera_type << " camera\n"
<< "Settings: MSAA " << (m_msaa_samples > 1 ? std::to_string(m_msaa_samples) + "x" : "off")
<< ", FXAA " << on_off(config.get_bool(SETTING_OPENGL_FXAA_ENABLED))
<< ", scene cache " << on_off(config.get_bool(SETTING_OPENGL_SCENE_CACHE)) << ", VSync " << m_vsync << '\n'
<< "Realistic: ";
if (config.get_bool(SETTING_OPENGL_REALISTIC_MODE))
out << "on, Phong " << on_off(config.get_bool(SETTING_OPENGL_REALISTIC_PHONG)) << ", SSAO "
<< on_off(config.get_bool(SETTING_OPENGL_PHONG_SSAO)) << ", shadows " << config.get(SETTING_OPENGL_REALISTIC_SHADOWS)
<< ", in Preview " << on_off(config.get_bool(SETTING_OPENGL_REALISTIC_PREVIEW)) << '\n';
else
out << "off\n";
// The Preview scenes depend on the toolpaths, which depend on the presets it was sliced with.
const PresetBundle& presets = *wxGetApp().preset_bundle;
auto preset_name = [](const PresetCollection& collection) {
return collection.get_selected_preset_name() + (collection.get_edited_preset().is_dirty ? " (modified)" : "");
};
out << "Presets: " << preset_name(presets.printers) << ", " << preset_name(presets.prints) << '\n'
<< "Scene: OrcaSliced Combo, " << PASS_FRAMES << " frames per pass\n";
const GCodeViewer& gcode_viewer = wxGetApp().plater()->get_preview_canvas3D()->get_gcode_viewer();
if (gcode_viewer.has_data())
out << "Toolpaths: " << gcode_viewer.get_vertices_count() << " vertices, " << gcode_viewer.get_layers_count() << " layers\n";
out << '\n';
std::vector<FrameTimeStats> stats;
for (const SceneResult& result : m_results)
stats.push_back(frame_time_stats(result.frame_ms));
auto row = [&out, &stats](const char* label, double FrameTimeStats::*value) {
out << std::left << std::setw(22) << label << std::right;
for (const FrameTimeStats& s : stats)
out << std::setw(10) << s.*value;
out << '\n';
};
out << std::fixed << std::setprecision(1) << std::setw(22) << "";
for (const SceneResult& result : m_results)
out << std::right << std::setw(10) << result.name;
out << '\n';
row("Average FPS", &FrameTimeStats::fps);
out << std::setprecision(2) << "Frame time (ms)\n";
row(" average", &FrameTimeStats::average_ms);
row(" median", &FrameTimeStats::median_ms);
row(" 95th percentile", &FrameTimeStats::p95_ms);
row(" 99th percentile", &FrameTimeStats::p99_ms);
row(" maximum", &FrameTimeStats::max_ms);
for (const SceneResult& result : m_results) {
out << "\nRender timings, " << result.name << " (ms per frame)\n";
if (result.sections.empty()) {
out << " not supported by the graphics driver\n";
continue;
}
out << std::setw(22) << "" << std::setw(10) << "CPU" << std::setw(10) << "GPU" << '\n';
double cpu_ms = 0.0;
double gpu_ms = 0.0;
for (const FrameProfiler::Section& section : result.sections) {
out << " " << std::left << std::setw(20) << section.name << std::right << std::setw(10) << section.cpu_ms << std::setw(10)
<< section.gpu_ms << '\n';
cpu_ms += section.cpu_ms;
gpu_ms += section.gpu_ms;
}
out << " " << std::left << std::setw(20) << "total" << std::right << std::setw(10) << cpu_ms << std::setw(10) << gpu_ms << '\n';
}
return out.str();
}
} // namespace
void run_scene_benchmark()
{
Plater* plater = wxGetApp().plater();
if (plater == nullptr || !wxGetApp().is_editor())
return;
if (s_benchmark != nullptr) {
if (s_benchmark->is_running()) {
s_benchmark->Raise();
return;
}
s_benchmark->Close();
}
MessageDialog confirm(wxGetApp().mainframe,
_L("The benchmark replaces the current project with the OrcaSliced Combo and slices it. It turns the camera "
"around the model in Prepare and in Preview, then moves the layer slider through the sliced layers, timing "
"every frame, and shows the results.") + "\n\n" +
_L("It uses the current graphics settings and usually takes less than a minute. The main window stays "
"locked until it finishes."),
_L("Benchmark 3D Scene"), wxICON_INFORMATION | wxOK | wxCANCEL);
confirm.SetButtonLabel(wxID_OK, _L("Start"), true);
if (confirm.ShowModal() != wxID_OK || plater->new_project() == wxID_CANCEL)
return;
const std::vector<MenuFactory::HandyModel>& models = MenuFactory::handy_models();
const auto combo = std::find_if(models.begin(), models.end(),
[](const MenuFactory::HandyModel& model) { return std::strcmp(model.key, "orcasliced_combo") == 0; });
MenuFactory::load_handy_model(size_t(combo - models.begin()));
(new SceneBenchmarkDialog())->Show();
}
} // namespace GUI
} // namespace Slic3r
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <vector>
namespace Slic3r {
namespace GUI {
struct FrameTimeStats
{
double fps{ 0.0 };
double average_ms{ 0.0 };
double median_ms{ 0.0 };
double p95_ms{ 0.0 };
double p99_ms{ 0.0 };
double max_ms{ 0.0 };
};
// Nearest-rank percentiles, so every value is a measured frame time.
FrameTimeStats frame_time_stats(std::vector<double> frame_ms);
// Loads the OrcaSliced Combo into a new project and turns the camera around it in Prepare, then in
// Preview once sliced, then moves the layer slider through the layers, and reports the frame times and
// render timings of the three scenes.
void run_scene_benchmark();
} // namespace GUI
} // namespace Slic3r
+1
View File
@@ -9,6 +9,7 @@ add_executable(${_TEST_NAME}_tests
test_device_manager_integration.cpp
test_web_media_controller.cpp
test_scene_raycaster.cpp
test_scene_benchmark.cpp
test_lazy.cpp
test_prebuild_queue.cpp
test_staged_build.cpp
+2 -1
View File
@@ -238,7 +238,8 @@ TEST_CASE("Native command catalog includes the Help and developer-mode commands"
const Slic3r::GUI::NativeCommand* first = find("help_keyboard_shortcuts");
REQUIRE(first != nullptr);
for (const char* key : {"help_setup_wizard", "help_open_config_folder", "help_troubleshoot", "help_network_test",
"help_tip_of_the_day", "help_check_updates", "help_about", "open_wiki", "open_youtube"}) {
"help_benchmark_3d_scene", "help_tip_of_the_day", "help_check_updates", "help_about", "open_wiki",
"open_youtube"}) {
const Slic3r::GUI::NativeCommand* c = find(key);
REQUIRE(c != nullptr);
CHECK(c->group == first->group);
@@ -0,0 +1,57 @@
#include <catch2/catch_all.hpp>
#include "slic3r/GUI/SceneBenchmark.hpp"
#include <algorithm>
#include <numeric>
#include <random>
#include <vector>
using Catch::Matchers::WithinAbs;
using Slic3r::GUI::frame_time_stats;
using Slic3r::GUI::FrameTimeStats;
TEST_CASE("Frame time statistics take percentiles by nearest rank", "[SceneBenchmark]")
{
// 1 to 100 ms in shuffled order: the p-th percentile is the p-th frame time.
std::vector<double> frame_ms(100);
std::iota(frame_ms.begin(), frame_ms.end(), 1.0);
std::shuffle(frame_ms.begin(), frame_ms.end(), std::mt19937(42));
const FrameTimeStats stats = frame_time_stats(frame_ms);
CHECK_THAT(stats.average_ms, WithinAbs(50.5, 1e-9));
CHECK_THAT(stats.fps, WithinAbs(1000.0 / 50.5, 1e-9));
CHECK_THAT(stats.median_ms, WithinAbs(50.0, 1e-9));
CHECK_THAT(stats.p95_ms, WithinAbs(95.0, 1e-9));
CHECK_THAT(stats.p99_ms, WithinAbs(99.0, 1e-9));
CHECK_THAT(stats.max_ms, WithinAbs(100.0, 1e-9));
}
TEST_CASE("Frame time percentiles are measured frame times", "[SceneBenchmark]")
{
// 360 frames, as in a benchmark pass: rank 342 for p95 and rank 357 for p99.
std::vector<double> frame_ms(360);
std::iota(frame_ms.begin(), frame_ms.end(), 1.0);
const FrameTimeStats stats = frame_time_stats(frame_ms);
CHECK_THAT(stats.median_ms, WithinAbs(180.0, 1e-9));
CHECK_THAT(stats.p95_ms, WithinAbs(342.0, 1e-9));
CHECK_THAT(stats.p99_ms, WithinAbs(357.0, 1e-9));
}
TEST_CASE("Frame time statistics of a single frame are that frame", "[SceneBenchmark]")
{
const FrameTimeStats stats = frame_time_stats({ 4.0 });
CHECK_THAT(stats.fps, WithinAbs(250.0, 1e-9));
CHECK_THAT(stats.median_ms, WithinAbs(4.0, 1e-9));
CHECK_THAT(stats.p99_ms, WithinAbs(4.0, 1e-9));
CHECK_THAT(stats.max_ms, WithinAbs(4.0, 1e-9));
}
TEST_CASE("Frame time statistics of no frames are zero", "[SceneBenchmark]")
{
const FrameTimeStats stats = frame_time_stats({});
CHECK_THAT(stats.fps, WithinAbs(0.0, 1e-9));
CHECK_THAT(stats.average_ms, WithinAbs(0.0, 1e-9));
CHECK_THAT(stats.max_ms, WithinAbs(0.0, 1e-9));
}