updates
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m22s
CI / Windows Release Package (push) Has been cancelled

This commit is contained in:
2026-05-08 18:07:45 +10:00
parent eede6938cb
commit f322abf79a
24 changed files with 270 additions and 43 deletions

View File

@@ -105,7 +105,8 @@ bool OpenGLComposite::InitVideoIO()
MessageBoxA(NULL, initFailureReason.c_str(), title, MB_OK | MB_ICONERROR);
return false;
}
if (!mVideoIO->SelectPreferredFormats(videoModes, initFailureReason))
const bool outputAlphaRequired = mRuntimeHost && mRuntimeHost->ExternalKeyingEnabled();
if (!mVideoIO->SelectPreferredFormats(videoModes, outputAlphaRequired, initFailureReason))
goto error;
if (! CheckOpenGLExtensions())

View File

@@ -34,8 +34,8 @@ bool OpenGLRenderPipeline::RenderFrame(const RenderPipelineFrameContext& context
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputFramebuffer());
if (mOutputReady)
mOutputReady();
if (state.outputPixelFormat == VideoIOPixelFormat::V210)
PackOutputForV210(state);
if (state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10)
PackOutputFor10Bit(state);
glFlush();
const auto renderEndTime = std::chrono::steady_clock::now();
@@ -50,7 +50,7 @@ bool OpenGLRenderPipeline::RenderFrame(const RenderPipelineFrameContext& context
return true;
}
void OpenGLRenderPipeline::PackOutputForV210(const VideoIOState& state)
void OpenGLRenderPipeline::PackOutputFor10Bit(const VideoIOState& state)
{
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
glViewport(0, 0, state.outputPackTextureWidth, state.outputFrameSize.height);
@@ -64,10 +64,13 @@ void OpenGLRenderPipeline::PackOutputForV210(const VideoIOState& state)
const GLint outputResolutionLocation = glGetUniformLocation(mRenderer.OutputPackProgram(), "uOutputVideoResolution");
const GLint activeWordsLocation = glGetUniformLocation(mRenderer.OutputPackProgram(), "uActiveV210Words");
const GLint packFormatLocation = glGetUniformLocation(mRenderer.OutputPackProgram(), "uOutputPackFormat");
if (outputResolutionLocation >= 0)
glUniform2f(outputResolutionLocation, static_cast<float>(state.outputFrameSize.width), static_cast<float>(state.outputFrameSize.height));
if (activeWordsLocation >= 0)
glUniform1f(activeWordsLocation, static_cast<float>(ActiveV210WordsForWidth(state.outputFrameSize.width)));
if (packFormatLocation >= 0)
glUniform1i(packFormatLocation, state.outputPixelFormat == VideoIOPixelFormat::Yuva10 ? 2 : 1);
glDrawArrays(GL_TRIANGLES, 0, 3);
glUseProgram(0);
@@ -79,7 +82,7 @@ void OpenGLRenderPipeline::ReadOutputFrame(const VideoIOState& state, VideoIOOut
{
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
if (state.outputPixelFormat == VideoIOPixelFormat::V210)
if (state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10)
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
glReadPixels(0, 0, state.outputPackTextureWidth, state.outputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, outputFrame.bytes);

View File

@@ -30,7 +30,7 @@ public:
bool RenderFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
private:
void PackOutputForV210(const VideoIOState& state);
void PackOutputFor10Bit(const VideoIOState& state);
void ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame);
OpenGLRenderer& mRenderer;

View File

@@ -90,12 +90,17 @@ const char* kOutputPackFragmentShaderSource =
"layout(binding = 0) uniform sampler2D uOutputRgb;\n"
"uniform vec2 uOutputVideoResolution;\n"
"uniform float uActiveV210Words;\n"
"uniform int uOutputPackFormat;\n"
"in vec2 vTexCoord;\n"
"layout(location = 0) out vec4 fragColor;\n"
"vec3 rgbAt(int x, int y)\n"
"vec4 rgbaAt(int x, int y)\n"
"{\n"
" ivec2 size = ivec2(max(uOutputVideoResolution, vec2(1.0, 1.0)));\n"
" return clamp(texelFetch(uOutputRgb, ivec2(clamp(x, 0, size.x - 1), clamp(y, 0, size.y - 1)), 0).rgb, vec3(0.0), vec3(1.0));\n"
" return clamp(texelFetch(uOutputRgb, ivec2(clamp(x, 0, size.x - 1), clamp(y, 0, size.y - 1)), 0), vec4(0.0), vec4(1.0));\n"
"}\n"
"vec3 rgbAt(int x, int y)\n"
"{\n"
" return rgbaAt(x, y).rgb;\n"
"}\n"
"vec3 rgbToLegalYcbcr10(vec3 rgb)\n"
"{\n"
@@ -112,9 +117,35 @@ const char* kOutputPackFragmentShaderSource =
"{\n"
" return vec4(float(word & 255u), float((word >> 8) & 255u), float((word >> 16) & 255u), float((word >> 24) & 255u)) / 255.0;\n"
"}\n"
"vec4 bigEndianWordToBytes(uint word)\n"
"{\n"
" return vec4(float((word >> 24) & 255u), float((word >> 16) & 255u), float((word >> 8) & 255u), float(word & 255u)) / 255.0;\n"
"}\n"
"vec4 packAy10Word(ivec2 outCoord)\n"
"{\n"
" ivec2 size = ivec2(max(uOutputVideoResolution, vec2(1.0, 1.0)));\n"
" if (outCoord.x >= size.x)\n"
" return vec4(0.0);\n"
" int pixelBase = (outCoord.x / 2) * 2;\n"
" int y = outCoord.y;\n"
" vec4 rgba0 = rgbaAt(pixelBase + 0, y);\n"
" vec4 rgba1 = rgbaAt(pixelBase + 1, y);\n"
" vec3 c0 = rgbToLegalYcbcr10(rgba0.rgb);\n"
" vec3 c1 = rgbToLegalYcbcr10(rgba1.rgb);\n"
" float chroma = (outCoord.x & 1) == 0 ? round((c0.y + c1.y) * 0.5) : round((c0.z + c1.z) * 0.5);\n"
" float alpha = round(clamp(((outCoord.x & 1) == 0 ? rgba0.a : rgba1.a), 0.0, 1.0) * 1023.0);\n"
" float luma = (outCoord.x & 1) == 0 ? c0.x : c1.x;\n"
" uint word = ((uint(luma) & 1023u) << 22) | ((uint(chroma) & 1023u) << 12) | ((uint(alpha) & 1023u) << 2);\n"
" return bigEndianWordToBytes(word);\n"
"}\n"
"void main()\n"
"{\n"
" ivec2 outCoord = ivec2(gl_FragCoord.xy);\n"
" if (uOutputPackFormat == 2)\n"
" {\n"
" fragColor = packAy10Word(outCoord);\n"
" return;\n"
" }\n"
" if (float(outCoord.x) >= uActiveV210Words)\n"
" {\n"
" fragColor = vec4(0.0);\n"

View File

@@ -629,6 +629,9 @@ bool ParseParameterDefinition(const JsonValue& parameterJson, ShaderParameterDef
if (!ValidateShaderIdentifier(definition.id, "parameters[].id", manifestPath, error))
return false;
if (!OptionalStringField(parameterJson, "description", definition.description, "", manifestPath, error))
return false;
if (!ParseParameterDefault(parameterJson, definition, manifestPath, error) ||
!ParseParameterNumberField(parameterJson, "min", definition.minNumbers, manifestPath, error) ||
!ParseParameterNumberField(parameterJson, "max", definition.maxNumbers, manifestPath, error) ||
@@ -1974,6 +1977,8 @@ JsonValue RuntimeHost::SerializeLayerStackLocked() const
JsonValue parameter = JsonValue::MakeObject();
parameter.set("id", JsonValue(definition.id));
parameter.set("label", JsonValue(definition.label));
if (!definition.description.empty())
parameter.set("description", JsonValue(definition.description));
parameter.set("type", JsonValue(ShaderParameterTypeToString(definition.type)));
parameter.set("defaultValue", SerializeParameterValue(definition, DefaultValueForDefinition(definition)));

View File

@@ -604,6 +604,9 @@ bool ParseParameterDefinition(const JsonValue& parameterJson, ShaderParameterDef
if (!ValidateShaderIdentifier(definition.id, "parameters[].id", manifestPath, error))
return false;
if (!OptionalStringField(parameterJson, "description", definition.description, "", manifestPath, error))
return false;
if (!ParseParameterDefault(parameterJson, definition, manifestPath, error) ||
!ParseParameterNumberField(parameterJson, "min", definition.minNumbers, manifestPath, error) ||
!ParseParameterNumberField(parameterJson, "max", definition.maxNumbers, manifestPath, error) ||

View File

@@ -26,6 +26,7 @@ struct ShaderParameterDefinition
{
std::string id;
std::string label;
std::string description;
ShaderParameterType type = ShaderParameterType::Float;
std::vector<double> defaultNumbers;
std::vector<double> minNumbers;

View File

@@ -54,6 +54,8 @@ const char* VideoIOPixelFormatName(VideoIOPixelFormat format)
{
case VideoIOPixelFormat::V210:
return "10-bit YUV v210";
case VideoIOPixelFormat::Yuva10:
return "10-bit YUVA Ay10";
case VideoIOPixelFormat::Bgra8:
return "8-bit BGRA";
case VideoIOPixelFormat::Uyvy8:
@@ -64,7 +66,7 @@ const char* VideoIOPixelFormatName(VideoIOPixelFormat format)
bool VideoIOPixelFormatIsTenBit(VideoIOPixelFormat format)
{
return format == VideoIOPixelFormat::V210;
return format == VideoIOPixelFormat::V210 || format == VideoIOPixelFormat::Yuva10;
}
VideoIOPixelFormat ChoosePreferredVideoIOFormat(bool tenBitSupported)
@@ -80,6 +82,8 @@ unsigned VideoIOBytesPerPixel(VideoIOPixelFormat format)
return 2u;
case VideoIOPixelFormat::Bgra8:
return 4u;
case VideoIOPixelFormat::Yuva10:
return 4u;
case VideoIOPixelFormat::V210:
default:
return 0u;
@@ -90,6 +94,8 @@ unsigned VideoIORowBytes(VideoIOPixelFormat format, unsigned frameWidth)
{
if (format == VideoIOPixelFormat::V210)
return MinimumV210RowBytes(frameWidth);
if (format == VideoIOPixelFormat::Yuva10)
return MinimumYuva10RowBytes(frameWidth);
return frameWidth * VideoIOBytesPerPixel(format);
}
@@ -103,6 +109,11 @@ unsigned MinimumV210RowBytes(unsigned frameWidth)
return ((frameWidth + 5u) / 6u) * 16u;
}
unsigned MinimumYuva10RowBytes(unsigned frameWidth)
{
return ((frameWidth + 63u) / 64u) * 256u;
}
unsigned ActiveV210WordsForWidth(unsigned frameWidth)
{
return ((frameWidth + 5u) / 6u) * 4u;

View File

@@ -7,6 +7,7 @@ enum class VideoIOPixelFormat
{
Uyvy8,
V210,
Yuva10,
Bgra8
};
@@ -31,6 +32,7 @@ unsigned VideoIOBytesPerPixel(VideoIOPixelFormat format);
unsigned VideoIORowBytes(VideoIOPixelFormat format, unsigned frameWidth);
unsigned PackedTextureWidthFromRowBytes(unsigned rowBytes);
unsigned MinimumV210RowBytes(unsigned frameWidth);
unsigned MinimumYuva10RowBytes(unsigned frameWidth);
unsigned ActiveV210WordsForWidth(unsigned frameWidth);
V210CodeValues Rec709RgbToLegalV210(float red, float green, float blue);
std::array<uint8_t, 16> PackV210Block(const V210SixPixelBlock& block);

View File

@@ -95,7 +95,7 @@ public:
virtual ~VideoIODevice() = default;
virtual void ReleaseResources() = 0;
virtual bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error) = 0;
virtual bool SelectPreferredFormats(const VideoFormatSelection& videoModes, std::string& error) = 0;
virtual bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error) = 0;
virtual bool ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error) = 0;
virtual bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) = 0;
virtual bool Start() = 0;

View File

@@ -223,7 +223,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM
return true;
}
bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoModes, std::string& error)
bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error)
{
if (!output)
{
@@ -239,8 +239,15 @@ bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoMo
mState.formatStatusMessage += "DeckLink input does not report 10-bit YUV support for the configured mode; using 8-bit capture. ";
const bool outputTenBitSupported = OutputSupportsFormat(output, videoModes.output.displayMode, bmdFormat10BitYUV);
mState.outputPixelFormat = outputTenBitSupported ? VideoIOPixelFormat::V210 : VideoIOPixelFormat::Bgra8;
if (!outputTenBitSupported)
const bool outputTenBitYuvaSupported = OutputSupportsFormat(output, videoModes.output.displayMode, bmdFormat10BitYUVA);
mState.outputPixelFormat = outputAlphaRequired
? (outputTenBitYuvaSupported ? VideoIOPixelFormat::Yuva10 : VideoIOPixelFormat::Bgra8)
: (outputTenBitSupported ? VideoIOPixelFormat::V210 : VideoIOPixelFormat::Bgra8);
if (outputAlphaRequired && outputTenBitYuvaSupported)
mState.formatStatusMessage += "External keying requires alpha; using 10-bit YUVA output. ";
else if (outputAlphaRequired)
mState.formatStatusMessage += "External keying requires alpha, but DeckLink output does not report 10-bit YUVA support for the configured mode; using 8-bit BGRA output. ";
else if (!outputTenBitSupported)
mState.formatStatusMessage += "DeckLink output does not report 10-bit YUV support for the configured mode; using 8-bit BGRA output. ";
int deckLinkOutputRowBytes = 0;

View File

@@ -22,7 +22,7 @@ public:
void ReleaseResources() override;
bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error) override;
bool SelectPreferredFormats(const VideoFormatSelection& videoModes, std::string& error) override;
bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error) override;
bool ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error) override;
bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) override;
bool Start() override;

View File

@@ -6,6 +6,8 @@ BMDPixelFormat DeckLinkPixelFormatForVideoIO(VideoIOPixelFormat format)
{
case VideoIOPixelFormat::V210:
return bmdFormat10BitYUV;
case VideoIOPixelFormat::Yuva10:
return bmdFormat10BitYUVA;
case VideoIOPixelFormat::Bgra8:
return bmdFormat8BitBGRA;
case VideoIOPixelFormat::Uyvy8:
@@ -18,6 +20,8 @@ VideoIOPixelFormat VideoIOPixelFormatFromDeckLink(BMDPixelFormat format)
{
if (format == bmdFormat10BitYUV)
return VideoIOPixelFormat::V210;
if (format == bmdFormat10BitYUVA)
return VideoIOPixelFormat::Yuva10;
if (format == bmdFormat8BitBGRA)
return VideoIOPixelFormat::Bgra8;
return VideoIOPixelFormat::Uyvy8;