Texture Drawing
This addon provides a solution to synchronize a texture modification.
Overview
The logic of this add-on is to share drawing points details (position, color, .. see DrawinPoint) between users.
A sample pen is included, drawing point when detecting drawable blocking surfaces (see the BlockingContact addon).
State authority
The logic is split between pens, and drawing surfaces, both having network behaviours, and potentially different state authority between a pen and its target drawing:
- the pen state authority is the grabber of the pen
- the drawing surface state authority might not be the drawing user, as several users might be drawing at the same time on the surface.
Drawing steps
When a new drawing part is added by a pen, first:
- the pen detect that a drawing point that should be added to a drawing surface (see TexturePen), and prepare its info (coordinates, ...).
- the pen draws a temporary line on the texture, for immediate display (the drawing surface will actually redraw it again once the network communication has occured))
- the latest points to draw for a pen are stored in a networked ring buffer on it (see TextureDrawer), so that all users can be aware of its will to add points on the drawing
Then, the drawing surface, on all clients:
- detects that a pen has new points for it,
- draws them on the actual texture (see TextureSurface).
- store these new drawing point in its local cache. See TextureDrawing
Texture drawing
The addon provides two ways to edit a texture:
- the default solution uses a specific drawing shader, made with Shader Graph, and so only compatible with URP and HDRP render pipelines. This solution is more suitable for high resolution textures.
- the alternative solution edits the texture using the
ProtoTurtle.BitmapDrawing
third party solution, which provides a bitmap drawing API.
Shader-based drawing
The LinePainter
shader can draw a line on a texture. It contains a subgraph LineSDK determining the distance from a point to the drawn line.
The drawing logic is to:
- use a render texture in the drawing surface material
- every time a line needs to be drawn, use
Graphics.Blit
to feed the shader with the current render texture and line details, and store the resulting results
Class details
TexturePen
The TexturePen
is located on the pen (with a BlockableTip
component) and tries to detect a contact with a BlockingSurface
component, having also a TextureDrawing
component.
When a contact is detected, the local list of points to draw is updated. Then for each point, the method AddDrawingPoint()
of TextureDrawer
is called during the FixedUpdateNetwork()
.
TextureDrawer
This is subclass of the RingBufferSyncBehaviour
class from DataSyncHelpers addon.
It is used to record the drawing points that must be edited on the texture when a contact is detected between the pen and the drawing surface.
To do that, the AddDrawingPoint()
method is called by the TexturePen
to add a DrawingPoint
to the list of points that should eventually be placed on the TextureSurface
.
When adding a point locally, or when remote players are informed of new points thanks to the RingBufferSyncBehaviour
's OnNewEntries
call back, it requests TextureDrawing
to add a new point.
Note:
- filling the drawer ring buffer too quickly might lead to data loss (in the current settings, it should not occur in normal network conditions). To prevent this, either throttle the transmission of points with
TexturePen.maxPointInsertionPerSeconds
, or increase theRingBufferSyncBehaviour.BUFFER_SIZE
TextureDrawing
When adding a new drawing point, if a line was not yet finished for the requesting TextureDrawer
, the TextureDrawing
creates a line between the previous point and the new one.
Several drawers can have drawings in parallel, as a TextureDrawing
keeps a cache of the latest point drawn per drawer.
The TextureDrawing
finally calls the Draw()
method on the referenced TextureSurface
, to add a point or draw a line.
Late joiners
For late joiners, TextureDrawing
uses the Fusion 2 streaming API, to send the complete list of points added from the start.
TextureDrawing subclasses StreamingSyncBehaviour and changes its logic.
While a StreamingSyncBehaviour
is built to send data in real time, and then share them to late joiners:
- here the
DrawingPoints
data are just stored in a local cache (the real time transmission has already been handled by theTextureDrawer
network var) - for late joiners, the reception data logic is changed, to mix between the data already received previously through the network var of the
TextureDrawers
(usually pretty quickly), and the full data cache that takes a few frame to be received.
Merging those data requires a reconciliation, as the last point in the full cache might also have been received first through the TextureDrawer
earlier (the same point might be received both through the streaming API providing the full cache backup, and through the TextureDrawer networked var provide real time data), and as those points could be associated to unfinished lines.
A simple way to solve this issue is to go through all the points stored in the full cache, and add a line end point for all drawing lines that were not finished upon transmission. The TextureDrawer
data being more recent, they will in any case finish any pending lines again.
Note:
- another way to solve this would be to use the full byte count, to detect duplicate points precisely.
- this approach might lead to different drawing on each client if people draw on the same pixel at the same time. If it is an issue, another approach would be to use a
RingBufferLosslessSyncBehaviour
subclass instead of aStreamingSyncBehaviour
: drawer not having authority on the drawing would draw temporary lines, and upon reception of the TextureDrawing "confirmation", the actual lines would be drawn.
TextureSurface
TextureSurface
references the Renderer
component and contains the utility methods for textures editing: initialize the texture, change the texture color, draw a point, draw a line.
So, this class is not linked to the network part.
It implements the IRenderTextureProvider
interface (from the DataSyncHelpers
addon). The onRedrawRequired
event is raised when the texture has been edited externally, and TextureDrawing
subscribes to it to redraw all the point if this happens.
DrawingPoint
This class defines the drawing points of the surface (position, color, pressure and a reference Id). This referenceId
used either to store the target TextureDrawing
, or the source TextureDrawer
.
It implements the RingBuffer.IRingBufferEntry
interface of the DataSyncHelpers
addon.
Dependencies
- DataSyncHelpers addon
- BlockingContact addon
Demo
A demo scene can be found in the Assets\Photon\FusionAddons\TextureDrawing\Demo\Scenes\
folder.
Download
This addon latest version is included into the Industries addon project
It is also included into the free XR addon project
Supported topologies
- shared mode
Third party
- ProtoTurtle.BitmapDrawing, MIT license, https://github.com/ProtoTurtle/UnityBitmapDrawing
Changelog
- Version 2.0.0: First release