GPUOffscreen.draw_view3d() is 25 to 50 times slower #89204

Closed
opened 2021-06-16 18:16:54 +02:00 by Christian Stolze · 23 comments

System Information
Operating system: macOS Bis Sur 11.2.1
Graphics card: Intel Iris Plus Graphics 1536 MB

Blender Version
Broken: since 2.80
Worked: 2.79b

Short description of error

I am currently working on an add-on which enables a live view of the viewport in a volumetric lightfield display. The display requires to render a scene from 32 to 100 different views from different angles. I use the gpu.types.GPUOffscreen class and it's internal method draw_view3d() to obtain the different views by manually calculating the view and projection matrices as required.

@GottfriedHofmann put my attention to the fact that this function used to be a lot faster in Blender 2.79b. Since Blender 2.80 it is 25 to 50 times slower. At the current speed of draw_view3d(), realizing a rendering of the required amount of views is not possible in a meaningful time. Since it was much faster in 2.79b, I hope it is possible to revert to this.

Exact steps for others to reproduce the error
Here is a sample code:

import bpy, gpu
import timeit
from bgl import *
from math import *
from mathutils import *

# some timer variable
start_multi_view = 0

class TEST_OT_drawview3d(bpy.types.Operator):

    bl_idname = "test.drawview3d"
    bl_label = "Render the scene from different views into offscreen"
    bl_options = {'REGISTER'}

    _handle_viewDrawing = []

    # only execute in viewport
    @classmethod
    def poll(cls, context):
        return context.area.type == 'VIEW_3D'


    # invoke the operator
    def invoke(self, context, event):

        self.viewCone = 58
        self.aspectRatio = 0.75
        self.focalPlane = 5.5

        self.rows = 8
        self.columns = 11
        self.totalViews = self.rows * self.columns
        self.quiltWidth = 4026
        self.quiltHeight = 4096
        self.viewWidth = self.quiltWidth / self.columns
        self.viewHeight = self.quiltHeight / self.rows

        self.viewOffscreens = []


        - PREPARE THE OFFSCREENS
        ################################################################

        - CREATE OFFSCREENS FOR DRAWING
        - create a list for the GPUOffscreens of the different views
        for view in range(0, self.totalViews, 1):

            # API FOR BLENDER 2.79b
            if bpy.app.version < (2, 80, 0):

                self.viewOffscreens.append(gpu.offscreen.new(int(self.viewWidth), int(self.viewHeight)))

            # API FOR BLENDER 2.8+
            else:

                self.viewOffscreens.append(gpu.types.GPUOffScreen(int(self.viewWidth), int(self.viewHeight)))



        - PREPARE THE OVERRIDE CONTEXT
        ################################################################

        # we use the current area
        area = context.area

        # find the correct region
        for region in area.regions:
            if region.type == "WINDOW":

                # create an override context for the drawing operations later
                for space in area.spaces:
                    if space.type == "VIEW_3D":

                        # create an override context
                        self.override = context.copy()

                        self.override['area'] = area
                        self.override['region'] = region
                        self.override['space_data'] = space
                        self.override['scene'] = context.scene
                        if not bpy.app.version < (2, 80, 0): self.override['view_layer'] = context.view_layer

                    break

                break

        - HANDLERS FOR VIEW RENDERING
        - ++++++++++++++++++++++++++++++
        for view in range(0, self.totalViews, 1):
            self._handle_viewDrawing.append(bpy.types.SpaceView3D.draw_handler_add(self.drawViewToOffscreen, (context, view), 'WINDOW', 'POST_PIXEL'))

        return {'FINISHED'}


    # set up the camera for each view and the shader of the rendering object
    def setupVirtualCameraForView(self, camera, view, viewMatrix, projectionMatrix):

        # if a camera is given
        if camera != None:

            # The field of view set by the camera
            fov = 2.0 * atan(1 / projectionMatrix[1][1])

            # calculate cameraSize from its distance to the focal plane and the FOV
            cameraDistance = self.focalPlane
            cameraSize = cameraDistance * tan(fov / 2)

            # start at viewCone * 0.5 and go up to -viewCone * 0.5
            offsetAngle = (0.5 - view / (self.totalViews - 1)) * radians(self.viewCone)

            # calculate the offset that the camera should move
            offset = cameraDistance * tan(offsetAngle)

            # translate the view matrix (position) by the calculated offset in x-direction
            viewMatrix = Matrix.Translation((offset, 0, 0)) * viewMatrix

            # modify the projection matrix, relative to the camera size and aspect ratio
            projectionMatrix[0][2] += offset / (cameraSize * self.aspectRatio)

        # return the projection matrix
        return viewMatrix, projectionMatrix


    # Draw function which copies data from the 3D View
    def drawViewToOffscreen(self, context, view):
        global start_multi_view

        # if this function call belongs to the first view
        if view == 0:

            # we start some timer
            start_multi_view = timeit.default_timer()

            print("--------------------------")
            print("Start rendering %i views at a resolution of %i x %i" % (self.totalViews, self.viewWidth, self.viewHeight))
            print("--------------------------")

        # select camera that belongs to the view
        camera = context.scene.camera

        if camera != None:

            - PREPARE VIEW & PROJECTION MATRIX
            - ++++++++++++++++++++++++++++++++++++++++++++++++
            # get camera's modelview matrix
            view_matrix = camera.matrix_world.copy()

            # correct for the camera scaling
            view_matrix = view_matrix * Matrix.Scale(1/camera.scale.x, 4, (1, 0, 0))
            view_matrix = view_matrix * Matrix.Scale(1/camera.scale.y, 4, (0, 1, 0))
            view_matrix = view_matrix * Matrix.Scale(1/camera.scale.z, 4, (0, 0, 1))

            # calculate the inverted view matrix because this is what the draw_view_3D function requires
            view_matrix = view_matrix.inverted_safe()

            # API FOR BLENDER 2.79b
            if bpy.app.version < (2, 80, 0):

                # get the camera's projection matrix
                projection_matrix = camera.calc_matrix_camera(
                        x = self.viewWidth,
                        y = self.viewHeight,
                        scale_x = 1.0,
                        scale_y = (self.rows / self.columns) / self.aspectRatio,
                    )

            # API FOR BLENDER 2.8+
            else:

                # get the camera's projection matrix
                projection_matrix = camera.calc_matrix_camera(
                        depsgraph=context.view_layer.depsgraph,
                        x = self.viewWidth,
                        y = self.viewHeight,
                        scale_x = 1.0,
                        scale_y = (self.rows / self.columns) / self.aspectRatio,
                    )
            # calculate the offset-projection of the current view
            view_matrix, projection_matrix = self.setupVirtualCameraForView(camera, view, view_matrix, projection_matrix)


            - RENDER THE VIEW INTO THE OFFSCREEN
            - ++++++++++++++++++++++++++++++++++++++++++++++++
            - NOTE: - the draw_view3d method does not apply the color management
            - - filed bug report (on 2020-12-28): https://developer.blender.org/T84227
            # 		- for some reason this call is much slower than in Blender 2.79b

# ------>   AND HERE STARTS THE BOTTLENECK
            start_test = timeit.default_timer()

            # API FOR BLENDER 2.79b
            if bpy.app.version < (2, 80, 0):

                # draw the viewport rendering to the offscreen for the current view
                self.viewOffscreens[view].draw_view3d(
                    scene=context.scene,
                    view3d=self.override['space_data'],
                    region=self.override['region'],
                    modelview_matrix=view_matrix,
                    projection_matrix=projection_matrix)

            # API FOR BLENDER 2.8+
            else:

                # draw the viewport rendering to the offscreen for the current view
                self.viewOffscreens[view].draw_view3d(
                    scene=context.scene,
                    view_layer=context.view_layer,
                    view3d=self.override['space_data'],
                    region=self.override['region'],
                    view_matrix=view_matrix,
                    projection_matrix=projection_matrix)


            print("draw_view3d (view: ", view, ") took: ", timeit.default_timer() - start_test, "s")
# ------>   AND HERE ENDS THE BOTTLENECK

            # if this function call belongs to the first view
            if view == self.totalViews - 1:

                print("--------------------------")
                print("Rendering  all views took: ", timeit.default_timer() - start_multi_view, "s")

        else:

            raise AttributeError("Please activate a camera.")


def register():

    bpy.utils.register_class(TEST_OT_drawview3d)

def unregister():

    bpy.utils.unregister_class(TEST_OT_drawview3d)

if __name__ == "__main__":
    register()

Please do the following:

  1. Start Blender in command line mode.
  2. Copy & paste the code.
  3. Run it.
  4. Go to a SpaceView3D, press F3, and type "offscreen" into the search field.
  5. Run the "test.drawview3d" operator.

It will then create 88 offscreen. Each of these of offscreens will call its draw_view3D() method to draw the viewport into its offscreen texture every time something in the scene changes. It will print how long this procedure takes to the console. On my computer the results are as follwing:

  • Blender 2.79b: 10 to 25 ms in total
  • Blender 2.83, 2.93, and 3.0: 500 to 650 ms in total
**System Information** Operating system: macOS Bis Sur 11.2.1 Graphics card: Intel Iris Plus Graphics 1536 MB **Blender Version** Broken: since 2.80 Worked: 2.79b **Short description of error** I am currently working on an add-on which enables a live view of the viewport in a volumetric lightfield display. The display requires to render a scene from 32 to 100 different views from different angles. I use the `gpu.types.GPUOffscreen` class and it's internal method `draw_view3d()` to obtain the different views by manually calculating the view and projection matrices as required. @GottfriedHofmann put my attention to the fact that this function used to be a lot faster in Blender 2.79b. Since Blender 2.80 it is 25 to 50 times slower. At the current speed of `draw_view3d()`, realizing a rendering of the required amount of views is not possible in a meaningful time. Since it was much faster in 2.79b, I hope it is possible to revert to this. **Exact steps for others to reproduce the error** Here is a sample code: ``` import bpy, gpu import timeit from bgl import * from math import * from mathutils import * # some timer variable start_multi_view = 0 class TEST_OT_drawview3d(bpy.types.Operator): bl_idname = "test.drawview3d" bl_label = "Render the scene from different views into offscreen" bl_options = {'REGISTER'} _handle_viewDrawing = [] # only execute in viewport @classmethod def poll(cls, context): return context.area.type == 'VIEW_3D' # invoke the operator def invoke(self, context, event): self.viewCone = 58 self.aspectRatio = 0.75 self.focalPlane = 5.5 self.rows = 8 self.columns = 11 self.totalViews = self.rows * self.columns self.quiltWidth = 4026 self.quiltHeight = 4096 self.viewWidth = self.quiltWidth / self.columns self.viewHeight = self.quiltHeight / self.rows self.viewOffscreens = [] - PREPARE THE OFFSCREENS ################################################################ - CREATE OFFSCREENS FOR DRAWING - create a list for the GPUOffscreens of the different views for view in range(0, self.totalViews, 1): # API FOR BLENDER 2.79b if bpy.app.version < (2, 80, 0): self.viewOffscreens.append(gpu.offscreen.new(int(self.viewWidth), int(self.viewHeight))) # API FOR BLENDER 2.8+ else: self.viewOffscreens.append(gpu.types.GPUOffScreen(int(self.viewWidth), int(self.viewHeight))) - PREPARE THE OVERRIDE CONTEXT ################################################################ # we use the current area area = context.area # find the correct region for region in area.regions: if region.type == "WINDOW": # create an override context for the drawing operations later for space in area.spaces: if space.type == "VIEW_3D": # create an override context self.override = context.copy() self.override['area'] = area self.override['region'] = region self.override['space_data'] = space self.override['scene'] = context.scene if not bpy.app.version < (2, 80, 0): self.override['view_layer'] = context.view_layer break break - HANDLERS FOR VIEW RENDERING - ++++++++++++++++++++++++++++++ for view in range(0, self.totalViews, 1): self._handle_viewDrawing.append(bpy.types.SpaceView3D.draw_handler_add(self.drawViewToOffscreen, (context, view), 'WINDOW', 'POST_PIXEL')) return {'FINISHED'} # set up the camera for each view and the shader of the rendering object def setupVirtualCameraForView(self, camera, view, viewMatrix, projectionMatrix): # if a camera is given if camera != None: # The field of view set by the camera fov = 2.0 * atan(1 / projectionMatrix[1][1]) # calculate cameraSize from its distance to the focal plane and the FOV cameraDistance = self.focalPlane cameraSize = cameraDistance * tan(fov / 2) # start at viewCone * 0.5 and go up to -viewCone * 0.5 offsetAngle = (0.5 - view / (self.totalViews - 1)) * radians(self.viewCone) # calculate the offset that the camera should move offset = cameraDistance * tan(offsetAngle) # translate the view matrix (position) by the calculated offset in x-direction viewMatrix = Matrix.Translation((offset, 0, 0)) * viewMatrix # modify the projection matrix, relative to the camera size and aspect ratio projectionMatrix[0][2] += offset / (cameraSize * self.aspectRatio) # return the projection matrix return viewMatrix, projectionMatrix # Draw function which copies data from the 3D View def drawViewToOffscreen(self, context, view): global start_multi_view # if this function call belongs to the first view if view == 0: # we start some timer start_multi_view = timeit.default_timer() print("--------------------------") print("Start rendering %i views at a resolution of %i x %i" % (self.totalViews, self.viewWidth, self.viewHeight)) print("--------------------------") # select camera that belongs to the view camera = context.scene.camera if camera != None: - PREPARE VIEW & PROJECTION MATRIX - ++++++++++++++++++++++++++++++++++++++++++++++++ # get camera's modelview matrix view_matrix = camera.matrix_world.copy() # correct for the camera scaling view_matrix = view_matrix * Matrix.Scale(1/camera.scale.x, 4, (1, 0, 0)) view_matrix = view_matrix * Matrix.Scale(1/camera.scale.y, 4, (0, 1, 0)) view_matrix = view_matrix * Matrix.Scale(1/camera.scale.z, 4, (0, 0, 1)) # calculate the inverted view matrix because this is what the draw_view_3D function requires view_matrix = view_matrix.inverted_safe() # API FOR BLENDER 2.79b if bpy.app.version < (2, 80, 0): # get the camera's projection matrix projection_matrix = camera.calc_matrix_camera( x = self.viewWidth, y = self.viewHeight, scale_x = 1.0, scale_y = (self.rows / self.columns) / self.aspectRatio, ) # API FOR BLENDER 2.8+ else: # get the camera's projection matrix projection_matrix = camera.calc_matrix_camera( depsgraph=context.view_layer.depsgraph, x = self.viewWidth, y = self.viewHeight, scale_x = 1.0, scale_y = (self.rows / self.columns) / self.aspectRatio, ) # calculate the offset-projection of the current view view_matrix, projection_matrix = self.setupVirtualCameraForView(camera, view, view_matrix, projection_matrix) - RENDER THE VIEW INTO THE OFFSCREEN - ++++++++++++++++++++++++++++++++++++++++++++++++ - NOTE: - the draw_view3d method does not apply the color management - - filed bug report (on 2020-12-28): https://developer.blender.org/T84227 # - for some reason this call is much slower than in Blender 2.79b # ------> AND HERE STARTS THE BOTTLENECK start_test = timeit.default_timer() # API FOR BLENDER 2.79b if bpy.app.version < (2, 80, 0): # draw the viewport rendering to the offscreen for the current view self.viewOffscreens[view].draw_view3d( scene=context.scene, view3d=self.override['space_data'], region=self.override['region'], modelview_matrix=view_matrix, projection_matrix=projection_matrix) # API FOR BLENDER 2.8+ else: # draw the viewport rendering to the offscreen for the current view self.viewOffscreens[view].draw_view3d( scene=context.scene, view_layer=context.view_layer, view3d=self.override['space_data'], region=self.override['region'], view_matrix=view_matrix, projection_matrix=projection_matrix) print("draw_view3d (view: ", view, ") took: ", timeit.default_timer() - start_test, "s") # ------> AND HERE ENDS THE BOTTLENECK # if this function call belongs to the first view if view == self.totalViews - 1: print("--------------------------") print("Rendering all views took: ", timeit.default_timer() - start_multi_view, "s") else: raise AttributeError("Please activate a camera.") def register(): bpy.utils.register_class(TEST_OT_drawview3d) def unregister(): bpy.utils.unregister_class(TEST_OT_drawview3d) if __name__ == "__main__": register() ``` Please do the following: 1. Start Blender in command line mode. 2. Copy & paste the code. 3. Run it. 4. Go to a SpaceView3D, press F3, and type "offscreen" into the search field. 5. Run the "test.drawview3d" operator. It will then create 88 offscreen. Each of these of offscreens will call its `draw_view3D()` method to draw the viewport into its offscreen texture every time something in the scene changes. It will print how long this procedure takes to the console. On my computer the results are as follwing: - **Blender 2.79b:** 10 to 25 ms in total - **Blender 2.83, 2.93, and 3.0:** 500 to 650 ms in total

Added subscribers: @GottfriedHofmann, @regcs

Added subscribers: @GottfriedHofmann, @regcs
Member

Added subscriber: @JulianEisel

Added subscriber: @JulianEisel
Member

I'm pretty sure this is caused by the BPY offscreen functionality using final render quality. Eevee is designed to favor speed over quality for the viewport, and quality over speed in proper renders. The offscreen pipeline works with the latter and doesn't provide an option.

Exposing this as an option would be trivial to add. I did it to some internal offscreen functions already for VR. Don't think the BPY side of it would be difficult either.

(I think this is an important and simple thing to address finally, but technically this is not a bug but a missing API feature - at least if my analysis is right.)

I'm pretty sure this is caused by the BPY offscreen functionality using final render quality. Eevee is designed to favor speed over quality for the viewport, and quality over speed in proper renders. The offscreen pipeline works with the latter and doesn't provide an option. Exposing this as an option would be trivial to add. I did it to some internal offscreen functions already for VR. Don't think the BPY side of it would be difficult either. (I think this is an important and simple thing to address finally, but technically this is not a bug but a missing API feature - at least if my analysis is right.)

This issue was referenced by d03b26edbd

This issue was referenced by d03b26edbdc3a9fe87fde44bd8db8c4a67a36757

Added subscriber: @brecht

Added subscriber: @brecht

It's not about final render quality. The issue is that the framebuffers and associated textures are being reallocated each time, instead of being cached. I've committed a fix that speeds things up here.

The remainder of the cost seems to be in actual Eevee or Workbench rendering. That may be slower or faster depending on the scene, it's completely different and depends on the scene contents and settings, so hard to compare. In general there will be more overhead for simpler scenes in 2.80, but it can also be faster with more objects. Any optimizations there are outside the scope of the bug tracker.

It's not about final render quality. The issue is that the framebuffers and associated textures are being reallocated each time, instead of being cached. I've committed a fix that speeds things up here. The remainder of the cost seems to be in actual Eevee or Workbench rendering. That may be slower or faster depending on the scene, it's completely different and depends on the scene contents and settings, so hard to compare. In general there will be more overhead for simpler scenes in 2.80, but it can also be faster with more objects. Any optimizations there are outside the scope of the bug tracker.

Changed status from 'Needs Triage' to: 'Resolved'

Changed status from 'Needs Triage' to: 'Resolved'
Brecht Van Lommel self-assigned this 2021-06-16 20:49:57 +02:00

Many thanks for taking action so quickly, @brecht. I just built the new version and getting something between 120 to 300 ms now. Definitely better than before, but still one order of magnitude slower than in 2.79b.

Could you shortly explain, what has been so fundamentally different in 2.79b that offscreen rendering was so amazingly fast there? And is there any chance to get back to these levels or is that out of the scope with the current Workbench render engine (not talking about EEVEE here, I just tested the Workbench rendering)?

Edit: Unfortunately, the fix introduced a new bug :

Many thanks for taking action so quickly, @brecht. I just built the new version and getting something between 120 to 300 ms now. Definitely better than before, but still one order of magnitude slower than in 2.79b. Could you shortly explain, what has been so fundamentally different in 2.79b that offscreen rendering was so amazingly fast there? And is there any chance to get back to these levels or is that out of the scope with the current Workbench render engine (not talking about EEVEE here, I just tested the Workbench rendering)? Edit: Unfortunately, [the fix introduced a new bug ](https://developer.blender.org/rBd03b26edbdc3a9fe87fde44bd8db8c4a67a36757#306369):

This issue was referenced by c73be23e17

This issue was referenced by c73be23e176272da78b38f733fd56af09ebd7acf

Changed status from 'Resolved' to: 'Needs Triage'

Changed status from 'Resolved' to: 'Needs Triage'

Reopened since the commit had a bug.

Reopened since the commit had a bug.

Started to look a bit into the timings of the calls that are involved in the offscreen drawing (with the reverted Blender version rbc73be). Don't know if it helps, but got the following insights so far:

Python level:

  • measuring the wall time and CPU time in Python code: average of ~3 ms CPU time and 5 ms wall time for calling GPUOffscreen.draw_view3d()

Blender internal:

  • the 3 ms CPU time for drawing the Offscreen after a Python call to GPUOffscreen.draw_view3d() is spent in DRW_draw_render_loop_offscreen()
  • tried to figure out how these 3 ms are spent:
    • ~1 ms of those 3 ms is required to prepare everything and clean up
    • ~2 ms of those 3 ms are spent in DRW_draw_render_loop_ex(); these 2 ms are spent as following:
      • ~0.9 ms are required for preparing the drawing
      • ~1.1 ms is spent for drawing in drw_engines_draw_scene(); these 1.1 ms are spent as following:
        • Workbench engine: 0.3 ms
        • Overlay engine: 0.8 ms

Preliminary conclusion:

So, the biggest potential for improving the offscreen rendering speed is in:

  • the caching issue @brecht already tried to address with d03b26 prior to revering
  • improving the time required by the Overlay engine (don't know, if the naming is correct, but this is what engine->idname gave as output in the drw_engines_draw_scene loop)

Any idea what takes so long with the latter?

Edit: Timings are given for drawing 88 views from different angles one after another. Drawing the first view usually takes significantly longer (10 to 20 ms) than drawing the following views.

Started to look a bit into the timings of the calls that are involved in the offscreen drawing (with the reverted Blender version rbc73be). Don't know if it helps, but got the following insights so far: **Python level:** - measuring the wall time and CPU time in Python code: average of ~3 ms CPU time and 5 ms wall time for calling `GPUOffscreen.draw_view3d()` **Blender internal:** - the 3 ms CPU time for drawing the Offscreen after a Python call to `GPUOffscreen.draw_view3d()` is spent in `DRW_draw_render_loop_offscreen()` - tried to figure out how these 3 ms are spent: - ~1 ms of those 3 ms is required to prepare everything and clean up - ~2 ms of those 3 ms are spent in `DRW_draw_render_loop_ex()`; these 2 ms are spent as following: - ~0.9 ms are required for preparing the drawing - ~1.1 ms is spent for drawing in `drw_engines_draw_scene()`; these 1.1 ms are spent as following: - *Workbench engine:* 0.3 ms - *Overlay engine:* 0.8 ms **Preliminary conclusion:** So, the biggest potential for improving the offscreen rendering speed is in: - the caching issue @brecht already tried to address with d03b26 prior to revering - improving the time required by the Overlay engine (don't know, if the naming is correct, but this is what `engine->idname` gave as output in the `drw_engines_draw_scene` loop) Any idea what takes so long with the latter? **Edit:** Timings are given for drawing 88 views from different angles one after another. Drawing the first view usually takes significantly longer (10 to 20 ms) than drawing the following views.

In #89204#1177764, @JulianEisel wrote:
I'm pretty sure this is caused by the BPY offscreen functionality using final render quality. Eevee is designed to favor speed over quality for the viewport, and quality over speed in proper renders. The offscreen pipeline works with the latter and doesn't provide an option.

Exposing this as an option would be trivial to add. I did it to some internal offscreen functions already for VR. Don't think the BPY side of it would be difficult either.

(I think this is an important and simple thing to address finally, but technically this is not a bug but a missing API feature - at least if my analysis is right.)

Are you talking about an argument for ED_view3d_draw_offscreen , maybe even 'bool is_image_render' ? I tried setting that to false but it helped only little to not at all :-(

> In #89204#1177764, @JulianEisel wrote: > I'm pretty sure this is caused by the BPY offscreen functionality using final render quality. Eevee is designed to favor speed over quality for the viewport, and quality over speed in proper renders. The offscreen pipeline works with the latter and doesn't provide an option. > > Exposing this as an option would be trivial to add. I did it to some internal offscreen functions already for VR. Don't think the BPY side of it would be difficult either. > > (I think this is an important and simple thing to address finally, but technically this is not a bug but a missing API feature - at least if my analysis is right.) Are you talking about an argument for [ED_view3d_draw_offscreen ](https://developer.blender.org/diffusion/B/browse/master/source/blender/editors/include/ED_view3d_offscreen.h$44) , maybe even 'bool is_image_render' ? I tried setting that to false but it helped only little to not at all :-(

Changed status from 'Needs Triage' to: 'Confirmed'

Changed status from 'Needs Triage' to: 'Confirmed'

Ok, I think I found the reason why d03b26 did introduced a redrawing problem for the offscreen.

.../engines/workbench/workbench_engine.c:
In workbench_draw_scene() the "wpd->taa_sample" is not reset after the first drawing. The most-likely cause is that in workbench_antialiasing_engine_init() the property is only reset if certain conditions are met. One of the conditions is, the "is_navigating", which is probably true when the camera is moving(?), which would explain why the issue did not occur when the camera was moved.

I guess something similar is true for EEVEE somewhere in the engine, which would explain why there was a ghosting effect introduced in the offscreen when moving the object.

How to fix this?
I see several options, but don't know what would be the best:

1.) setting the wpd->is_navigating to trueif an object is moved.
2.) introduce another state variable in "wpd" which is set if offscreen rendering is active and handle the resetting in the corresponding functions.

Probably there are even better solutions since I don't have the overview that would be required to judge.
So I think, I am now at a point where I can't do anything without further input from you, @brecht, or anybody else with deeper insights.

Ok, I think I found the reason why d03b26 did introduced a redrawing problem for the offscreen. `.../engines/workbench/workbench_engine.c`: In `workbench_draw_scene()` the "wpd->taa_sample" is not reset after the first drawing. The most-likely cause is that in `workbench_antialiasing_engine_init()` the property is only reset if certain conditions are met. One of the conditions is, the "is_navigating", which is probably true when the camera is moving(?), which would explain why the issue did not occur when the camera was moved. I guess something similar is true for EEVEE somewhere in the engine, which would explain why there was a ghosting effect introduced in the offscreen when moving the object. **How to fix this?** I see several options, but don't know what would be the best: 1.) setting the `wpd->is_navigating` to `true`if an object is moved. 2.) introduce another state variable in "wpd" which is set if offscreen rendering is active and handle the resetting in the corresponding functions. Probably there are even better solutions since I don't have the overview that would be required to judge. So I think, I am now at a point where I can't do anything without further input from you, @brecht, or anybody else with deeper insights.

In #89204#1182799, @regcs wrote:
.../engines/workbench/workbench_engine.c:
In workbench_draw_scene() the "wpd->taa_sample" is not reset after the first drawing. The most-likely cause is that in workbench_antialiasing_engine_init() the property is only reset if certain conditions are met. One of the conditions is, the "is_navigating", which is probably true when the camera is moving(?), which would explain why the issue did not occur when the camera was moved.

Resetting the property all the time in workbench_antialiasing_engine_init() does not make a change, it seems like that function is not called for every frame.

Maybe RV3D_NAVIGATING is the key here?

> In #89204#1182799, @regcs wrote: > `.../engines/workbench/workbench_engine.c`: > In `workbench_draw_scene()` the "wpd->taa_sample" is not reset after the first drawing. The most-likely cause is that in `workbench_antialiasing_engine_init()` the property is only reset if certain conditions are met. One of the conditions is, the "is_navigating", which is probably true when the camera is moving(?), which would explain why the issue did not occur when the camera was moved. Resetting the property all the time in workbench_antialiasing_engine_init() does not make a change, it seems like that function is not called for every frame. Maybe [RV3D_NAVIGATING ](https://developer.blender.org/diffusion/B/browse/master/?grep=RV3D_NAVIGATING) is the key here?

@regcs had the idea to enforce RV3D_NAVIGATING for all offscreen rendering by adding
rv3d->rflag |= RV3D_NAVIGATING;
to .../editors/space_view3d/view3d_draw.c right after line 1647.

That way RV3D_NAVIGATING is always set for offscreen rendering which fixes the problem for the workbench engine but not for EEVEE. When using the latter engine, the grid does not update at all anymore, no matter how I navigate the viewport. Neither by rotating, nor by panning, dollying or using the walk- and fly navigations.

@regcs had the idea to enforce RV3D_NAVIGATING for all offscreen rendering by adding `rv3d->rflag |= RV3D_NAVIGATING;` to [.../editors/space_view3d/view3d_draw.c ](https://developer.blender.org/diffusion/B/browse/master/source/blender/editors/space_view3d/view3d_draw.c$1647) right after line 1647. That way RV3D_NAVIGATING is always set for offscreen rendering which fixes the problem for the workbench engine but not for EEVEE. When using the latter engine, the grid does not update at all anymore, no matter how I navigate the viewport. Neither by rotating, nor by panning, dollying or using the walk- and fly navigations.
I have created a new revision of https://developer.blender.org/rBd03b26edbdc3a9fe87fde44bd8db8c4a67a36757 that is rebased to 3.1 trunk: https://developer.blender.org/D13104

Before the rebase, commenting out viewport->depth_tx = depth; in https://developer.blender.org/diffusion/B/browse/master/source/blender/gpu/intern/gpu_viewport.c$210 would result in the drawing of one frame without visual artifacts and a subsequent crash. After the rebase, the crash does not happen anymore but the result seems to have the artifacts as well.

Before the rebase, commenting out *viewport->depth_tx = depth;* in https://developer.blender.org/diffusion/B/browse/master/source/blender/gpu/intern/gpu_viewport.c$210 would result in the drawing of one frame without visual artifacts and a subsequent crash. After the rebase, the crash does not happen anymore but the result seems to have the artifacts as well.

One important observation: The issue does not arise when the offscreen is fed with a different view- and/or projection matrix each time.

Here is a demo file that slightly modifies the view matrix each time before offscreen rendering is called, it seems to "fix" all of the aforementioned problems:

testfile_offscreen_viewscramble.blend

One important observation: The issue does not arise when the offscreen is fed with a different view- and/or projection matrix each time. Here is a demo file that slightly modifies the view matrix each time before offscreen rendering is called, it seems to "fix" all of the aforementioned problems: [testfile_offscreen_viewscramble.blend](https://archive.blender.org/developer/F11714105/testfile_offscreen_viewscramble.blend)

A short explanation for my proposed fix:

The issue was that the views of a GPUViewport are usually updated by the ED_render_scene_update() on depsgraph changes https://developer.blender.org/diffusion/B/browse/master/source/blender/windowmanager/intern/wm_init_exit.c$260. However, this function only handles GPUViewports which are bound to a SpaceView3D. Since this is not the case for the GPUViewport created for the offscreen rendering in Brecht's patch, we need to make sure that the views are updated before drawing into the offscreen.

D13235 re-introduces Brecht's patch and adds a function which takes care of the view updates.

A short explanation for my proposed fix: The issue was that the views of a GPUViewport are usually updated by the `ED_render_scene_update()` on depsgraph changes https://developer.blender.org/diffusion/B/browse/master/source/blender/windowmanager/intern/wm_init_exit.c$260. However, this function only handles GPUViewports which are bound to a SpaceView3D. Since this is not the case for the GPUViewport created for the offscreen rendering in Brecht's patch, we need to make sure that the views are updated before drawing into the offscreen. [D13235](https://archive.blender.org/developer/D13235) re-introduces Brecht's patch and adds a function which takes care of the view updates.

This issue was referenced by bba6fe83e2

This issue was referenced by bba6fe83e27eab7c99bd5bab3eb914189a0793d2

Changed status from 'Confirmed' to: 'Resolved'

Changed status from 'Confirmed' to: 'Resolved'
Sign in to join this conversation.
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No project
No Assignees
7 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: blender/blender#89204
No description provided.