Skip to content

Commit

Permalink
feat+refactor!!: add Spout broadcasting support.
Browse files Browse the repository at this point in the history
i am not 100% satisfied with this, but for now it can work.

you can now toggle the spout broadcasting option, and the stage will
be broadcasted both from the editor and from the proper stage viewer.

this preserves transparency and allows for using the output of vtubetiny
in many applications, without having to resort to window capture and
chroma keying.

this is only a x64 windows thing, so on other platforms enabling spout
will just do nothing.

i don't like the fact that i had to add a fullscreen blitting thing to
the rendering context abstraction, but we need to draw the stage to the
screen when we're in the stage viewer. maybe i'll revisit it later.
  • Loading branch information
purifetchi committed Jan 3, 2023
1 parent eca4bbc commit a1d20a7
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 1 deletion.
3 changes: 3 additions & 0 deletions VTTiny/Base/VTubeTiny.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Raylib_cs;
using VTTiny.Data;
using VTTiny.Editor;
using VTTiny.Rendering;
using VTTiny.Scenery;
using VTTiny.Serialization;

Expand Down Expand Up @@ -88,6 +89,8 @@ public void SetActiveStage(Stage stage)
{
ActiveStage?.Destroy();
ActiveStage = stage;

ActiveStage.RenderingContext.SetCanDrawFullscreen(Editor == null);
}

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions VTTiny/Data/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class Config
public Vector2Int Dimensions { get; set; } = new(800, 480);
public Color ClearColor { get; set; } = new(0, 255, 0, 255);
public int FPSLimit { get; set; } = 60;
public bool BroadcastViaSpout { get; set; } = false;

public IList<ActorConfig> Actors { get; set; }
public AssetDatabaseConfig AssetDatabase { get; set; }
Expand Down
23 changes: 23 additions & 0 deletions VTTiny/Rendering/FramebufferRenderingContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ internal class FramebufferRenderingContext : IRenderingContext
protected RenderTexture2D _renderTexture;
private bool _disposedValue;

private bool _drawInFullscreen;

/// <summary>
/// Sets up the render texture.
/// </summary>
Expand All @@ -28,6 +30,9 @@ public virtual void Begin(Color color)
public virtual void End()
{
Raylib.EndTextureMode();

if (_drawInFullscreen)
DrawFullscreen();
}

public Texture2D? GetFramebuffer()
Expand All @@ -41,6 +46,24 @@ public virtual void Resize(Vector2Int dimensions)
_renderTexture = Raylib.LoadRenderTexture(dimensions.X, dimensions.Y);
}

public void SetCanDrawFullscreen(bool enabled)
{
_drawInFullscreen = enabled;
}

/// <summary>
/// Draws this framebuffer to the entire screen.
/// </summary>
private void DrawFullscreen()
{
Raylib.BeginDrawing();

var rect = new Rectangle(0, 0, _renderTexture.texture.width, -_renderTexture.texture.height);
Raylib.DrawTextureRec(_renderTexture.texture, rect, Vector2Int.Zero, Color.WHITE);

Raylib.EndDrawing();
}

protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
Expand Down
5 changes: 5 additions & 0 deletions VTTiny/Rendering/GenericRaylibRenderingContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public void Resize(Vector2Int dimensions)
Raylib.SetWindowSize(dimensions.X, dimensions.Y);
}

public void SetCanDrawFullscreen(bool _)
{
// The generic context only draws in full screen.
}

public void Dispose()
{

Expand Down
6 changes: 6 additions & 0 deletions VTTiny/Rendering/IRenderingContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ public interface IRenderingContext : IDisposable
/// </summary>
void End();

/// <summary>
/// Sets whether this rendering context can present itself in fullscreen.
/// </summary>
/// <param name="enabled">Whether this rendering context can present itself in fullscreen.</param>
void SetCanDrawFullscreen(bool enabled);

/// <summary>
/// Gets the framebuffer (if possible).
/// </summary>
Expand Down
84 changes: 84 additions & 0 deletions VTTiny/Rendering/SpoutFramebufferRenderingContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using Raylib_cs;
using Spout.NET;

namespace VTTiny.Rendering
{
#if ARCH_WINDOWS
/// <summary>
/// A framebuffer context that also sends the render texture after drawing to Spout.
/// </summary>
internal class SpoutFramebufferRenderingContext : FramebufferRenderingContext
{
/// <summary>
/// The sender name for Spout.
/// </summary>
private const string SPOUT_SENDER_NAME = "VTubeTiny";

/// <summary>
/// The Texture2D constant for OpenGL.
/// </summary>
private const uint GL_TEXTURE_2D = 3553u;

/// <summary>
/// The Spout sender.
/// </summary>
private SpoutSender _sender;

/// <summary>
/// Constructs a new spout framebuffer rendering context and sets up the spout sender.
/// </summary>
public SpoutFramebufferRenderingContext()
: base()
{
_sender = new SpoutSender();
_sender.CreateSender(SPOUT_SENDER_NAME, (uint)_renderTexture.texture.width, (uint)_renderTexture.texture.height, 0);
}

public override void Begin(Color _)
{
Raylib.BeginTextureMode(_renderTexture);

// We clear the stage with a blank color for transparency.
Raylib.ClearBackground(Color.BLANK);
}

public override void End()
{
base.End();

// Send the data straight to Spout.
_sender.SendTexture(_renderTexture.texture.id, GL_TEXTURE_2D, (uint)_renderTexture.texture.width, (uint)_renderTexture.texture.height, true, 0);
}

public override void Resize(Vector2Int dimensions)
{
base.Resize(dimensions);

// Resize the sender as well.
_sender.UpdateSender(SPOUT_SENDER_NAME, (uint)dimensions.X, (uint)dimensions.Y);
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);

if (disposing &&
_sender != null)
{
_sender.ReleaseSender(0);
_sender.Dispose();

_sender = null;
}
}
}
#else
/// <summary>
/// Mock framebuffer context for non-Windows platforms. (Spout is not supported on non-windows architectures)
/// </summary>

internal class SpoutFramebufferRenderingContext : FramebufferRenderingContext
{
}
#endif
}
5 changes: 5 additions & 0 deletions VTTiny/Scenery/Stage.Editor.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using ImGuiNET;
using VTTiny.Editor;
using VTTiny.Rendering;

namespace VTTiny.Scenery
{
Expand All @@ -21,6 +22,10 @@ internal void RenderEditorGUI()
if (ImGui.IsItemDeactivatedAfterEdit())
SetTargetFPS(TargetFPS);

var newBroadcastVal = EditorGUI.Checkbox("Enable Spout Broadcasting", BroadcastViaSpout);
if (newBroadcastVal != BroadcastViaSpout)
SetSpoutOrDefaultContext<FramebufferRenderingContext>(newBroadcastVal);

ImGui.Text("Actors");

foreach (var actor in _actors)
Expand Down
3 changes: 2 additions & 1 deletion VTTiny/Scenery/Stage.Serialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ internal Config PackageStageIntoConfig()
{
ClearColor = ClearColor,
Dimensions = Dimensions,
FPSLimit = TargetFPS
FPSLimit = TargetFPS,
BroadcastViaSpout = BroadcastViaSpout,
};

var actorList = new List<ActorConfig>();
Expand Down
28 changes: 28 additions & 0 deletions VTTiny/Scenery/Stage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ public partial class Stage
/// </summary>
public bool RenderBoundingBoxes { get; private set; } = false;

/// <summary>
/// Should this stage be broadcasted via Spout? (WINDOWS ONLY)
/// </summary>
public bool BroadcastViaSpout { get; private set; } = false;

/// <summary>
/// The instance of VTubeTiny this scene is tied to.
/// </summary>
Expand Down Expand Up @@ -88,6 +93,7 @@ public static Stage Blank(VTubeTiny vtubetiny)
RenderingContext = new GenericRaylibRenderingContext(),
TargetFPS = refreshRate,
VTubeTiny = vtubetiny,
BroadcastViaSpout = false,
AssetDatabase = new()
};
}
Expand All @@ -109,11 +115,31 @@ public Stage WithConfig(Config config)
ClearColor = config.ClearColor;
SetTargetFPS(config.FPSLimit);

SetSpoutOrDefaultContext<GenericRaylibRenderingContext>(config.BroadcastViaSpout);

CreateActorsFromConfigList(config.Actors);

return this;
}

/// <summary>
/// Enables spout and sets the rendering context or constructs a fallback rendering context specified by T.
/// </summary>
/// <param name="enabled">Whether it should be enabled</param>
/// <typeparam name="T">The rendering context to set when disabling spout.</typeparam>
public void SetSpoutOrDefaultContext<T>(bool enabled) where T: IRenderingContext, new()
{
if (BroadcastViaSpout == enabled)
return;

BroadcastViaSpout = enabled;

if (BroadcastViaSpout)
ReplaceRenderingContext(new SpoutFramebufferRenderingContext());
else
ReplaceRenderingContext(new T());
}

/// <summary>
/// Set the target FPS of this stage.
/// </summary>
Expand All @@ -130,7 +156,9 @@ public void SetTargetFPS(int fps)
/// <param name="context">The new rendering context.</param>
internal void ReplaceRenderingContext(IRenderingContext context)
{
RenderingContext?.Dispose();
RenderingContext = context;

ResizeStage(Dimensions);
}

Expand Down

0 comments on commit a1d20a7

Please sign in to comment.