Skip to content

Commit 23b17bf

Browse files
committed
Support for HDR in linear color space
1 parent b8626ec commit 23b17bf

File tree

8 files changed

+47
-30
lines changed

8 files changed

+47
-30
lines changed
36 KB
Binary file not shown.
9 KB
Binary file not shown.

Source/UnityCaptureFilter.cpp

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,15 @@ class CCaptureStream : CSourceStream, IKsPropertySet, IAMStreamConfig, IAMStream
106106
m_pReceiver = new SharedImageMemory();
107107
m_iUnscaledBufSize = 0;
108108
m_pUnscaledBuf = NULL;
109+
m_RGBA16Table = NULL;
109110
GetMediaType(0, &m_mt);
110111
}
111112

112113
virtual ~CCaptureStream()
113114
{
114115
delete m_pReceiver;
115-
if (m_pUnscaledBuf) delete[] m_pUnscaledBuf;
116+
if (m_pUnscaledBuf) free(m_pUnscaledBuf);
117+
if (m_RGBA16Table) free(m_RGBA16Table);
116118
}
117119

118120
private:
@@ -165,6 +167,7 @@ class CCaptureStream : CSourceStream, IKsPropertySet, IAMStreamConfig, IAMStream
165167
enum EType { JOB_NONE, JOB_RGBA8toRGB8, JOB_RGBA16toRGB8, JOB_RESIZE_LINEAR, JOB_MIRROR_HORIZONTAL } Type;
166168
const void *BufIn; void *BufOut;
167169
size_t Width, RowStart, RowEnd, RGBAInStride, ResizeToHeight, ResizeFromWidth, ResizeFromHeight;
170+
const uint8_t* RGBA16Table;
168171

169172
inline void Execute()
170173
{
@@ -214,9 +217,8 @@ class CCaptureStream : CSourceStream, IKsPropertySet, IAMStreamConfig, IAMStream
214217
void RGBA16toRGB8()
215218
{
216219
//16 bit color downscaling (HDR (16 bit floats) to BGR)
217-
float tmpf; uint16_t *tmpp, tmpr, tmpg, tmpb;
218-
#define F16toU8(s) ((s) & 0x8000 ? 0 : (*(uint32_t*)&tmpf = ((s) << 13) + 0x38000000, tmpf < 1.0f ? (int)(tmpf * 255.99f) : 255))
219-
#define RGBAF16toBGRU8(psrc) (tmpp = (uint16_t*)(psrc), tmpr = tmpp[0], tmpg = tmpp[1], tmpb = tmpp[2], (F16toU8(tmpr)<<16) | (F16toU8(tmpg)<<8) | F16toU8(tmpb))
220+
const uint8_t* ttbl = RGBA16Table;
221+
#define RGBAF16toBGRU8(psrc) ((ttbl[((uint16_t*)(psrc))[0]]<<16) | (ttbl[((uint16_t*)(psrc))[1]]<<8) | ttbl[((uint16_t*)(psrc))[2]])
220222
const uint64_t *src = (const uint64_t*)BufIn + (RowStart * RGBAInStride);
221223
uint8_t *dst = (uint8_t*)BufOut + (RowStart * Width * 3), *dstEnd = (uint8_t*)BufOut + (RowEnd * Width * 3);
222224
if (RGBAInStride != Width)
@@ -250,7 +252,6 @@ class CCaptureStream : CSourceStream, IKsPropertySet, IAMStreamConfig, IAMStream
250252
//For the final pixel we can't use 4 byte uint32_t copy so we call memcpy
251253
uint32_t FinalPixel = RGBAF16toBGRU8(src);
252254
memcpy(dst, &FinalPixel, 3);
253-
#undef F16toU8
254255
#undef RGBAF16toBGRU8
255256
}
256257

@@ -349,7 +350,7 @@ class CCaptureStream : CSourceStream, IKsPropertySet, IAMStreamConfig, IAMStream
349350
CCaptureStream* Owner;
350351
};
351352

352-
static void ProcessImage(int InWidth, int InHeight, int InStride, int InRGBABits, SharedImageMemory::EResizeMode ResizeMode, SharedImageMemory::EMirrorMode MirrorMode, uint8_t* InBuf, ProcessState* State)
353+
static void ProcessImage(int InWidth, int InHeight, int InStride, SharedImageMemory::EFormat Format, SharedImageMemory::EResizeMode ResizeMode, SharedImageMemory::EMirrorMode MirrorMode, uint8_t* InBuf, ProcessState* State)
353354
{
354355
const bool NeedResize = (InWidth != State->BufWidth || InHeight != State->BufHeight);
355356
if (NeedResize && ResizeMode == SharedImageMemory::RESIZEMODE_DISABLED)
@@ -372,18 +373,34 @@ class CCaptureStream : CSourceStream, IKsPropertySet, IAMStreamConfig, IAMStream
372373
DWORD UnscaledBufSize = (InWidth * InHeight * 3);
373374
if (State->Owner->m_iUnscaledBufSize != UnscaledBufSize)
374375
{
375-
if (State->Owner->m_pUnscaledBuf) delete[] State->Owner->m_pUnscaledBuf;
376-
State->Owner->m_pUnscaledBuf = new uint8_t[UnscaledBufSize];
376+
if (State->Owner->m_pUnscaledBuf) free(State->Owner->m_pUnscaledBuf);
377+
State->Owner->m_pUnscaledBuf = (uint8_t*)malloc(UnscaledBufSize);
377378
State->Owner->m_iUnscaledBufSize = UnscaledBufSize;
378379
}
379380
}
380381

382+
if (Format != SharedImageMemory::FORMAT_UINT8 && (!State->Owner->m_RGBA16Table || State->Owner->m_RGBA16TableFormat != Format))
383+
{
384+
//Build a 64k table that maps 16 bit float values (either linear SRGB or gamma RGB) to 8 bit color values
385+
const bool SRGB = (Format == SharedImageMemory::FORMAT_FP16_LINEAR);
386+
uint8_t* RGBA16Table = State->Owner->m_RGBA16Table;
387+
if (!RGBA16Table) RGBA16Table = State->Owner->m_RGBA16Table = (uint8_t*)malloc(0xFFFF+1);
388+
for(int i = 0; i <= 0xFFFF; i++)
389+
{
390+
float f;
391+
(i & 0x8000 ? f = 0 : (*(uint32_t*)&f = (i << 13) + 0x38000000));
392+
if (SRGB) f = (f <= 0.0031308f ? (f * 12.92f) : (powf(f, 1.0f / 2.4f) * 1.055f - 0.055f));
393+
RGBA16Table[i] = (f < 1.0f ? (uint8_t)(f * 255.9999f) : 255);
394+
}
395+
State->Owner->m_RGBA16TableFormat = Format;
396+
}
397+
381398
//Multi-threaded conversion of RGBA source to 8-bit BGR format while also eliminating possible row gaps (when stride != width)
382-
UCASSERT(InRGBABits == 8 || InRGBABits == 16);
383399
ProcessJob Job;
384-
Job.Type = (InRGBABits == 8 ? ProcessJob::JOB_RGBA8toRGB8 : ProcessJob::JOB_RGBA16toRGB8);
400+
Job.Type = (Format == SharedImageMemory::FORMAT_UINT8 ? ProcessJob::JOB_RGBA8toRGB8 : ProcessJob::JOB_RGBA16toRGB8);
385401
Job.BufIn = InBuf, Job.BufOut = (NeedResize ? State->Owner->m_pUnscaledBuf : State->Buf);
386402
Job.Width = InWidth, Job.RowStart = 0, Job.RowEnd = InHeight, Job.RGBAInStride = InStride;
403+
Job.RGBA16Table = State->Owner->m_RGBA16Table;
387404
State->Owner->m_ProcessWorkers.StartNewJob(Job);
388405

389406
if (NeedResize)
@@ -666,7 +683,7 @@ class CCaptureStream : CSourceStream, IKsPropertySet, IAMStreamConfig, IAMStream
666683
pMediaType->SetTemporalCompression(FALSE);
667684
return S_OK;
668685
}
669-
686+
670687
HRESULT OnThreadStartPlay() override
671688
{
672689
DebugLog("[OnThreadStartPlay] OnThreadStartPlay\n");
@@ -681,7 +698,8 @@ class CCaptureStream : CSourceStream, IKsPropertySet, IAMStreamConfig, IAMStream
681698
SharedImageMemory* m_pReceiver;
682699
ProcessWorkers m_ProcessWorkers;
683700
DWORD m_iUnscaledBufSize;
684-
BYTE* m_pUnscaledBuf;
701+
uint8_t *m_pUnscaledBuf, *m_RGBA16Table;
702+
SharedImageMemory::EFormat m_RGBA16TableFormat;
685703

686704
//IAMStreamControl
687705
HRESULT STDMETHODCALLTYPE StartAt(const REFERENCE_TIME *ptStart, DWORD dwCookie) override { return NOERROR; }

Source/UnityCapturePlugin.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ extern "C" __declspec(dllexport) void CaptureDeleteInstance(UnityCaptureInstance
7171
delete c;
7272
}
7373

74-
extern "C" __declspec(dllexport) int CaptureSendTexture(UnityCaptureInstance* c, void* TextureNativePtr, bool UseDoubleBuffering, SharedImageMemory::EResizeMode ResizeMode, SharedImageMemory::EMirrorMode MirrorMode)
74+
extern "C" __declspec(dllexport) int CaptureSendTexture(UnityCaptureInstance* c, void* TextureNativePtr, bool UseDoubleBuffering, SharedImageMemory::EResizeMode ResizeMode, SharedImageMemory::EMirrorMode MirrorMode, bool IsLinearColorSpace)
7575
{
7676
if (!c || !TextureNativePtr) return RET_ERROR_PARAMETER;
7777
if (g_GraphicsDeviceType != kUnityGfxRendererD3D11) return RET_ERROR_UNSUPPORTEDGRAPHICSDEVICE;
@@ -120,18 +120,18 @@ extern "C" __declspec(dllexport) int CaptureSendTexture(UnityCaptureInstance* c,
120120
ID3D11Texture2D* ReadTexture = c->Textures[c->UseDoubleBuffering && !c->AlternativeBuffer ? 1 : 0];
121121

122122
//Check texture format
123-
int RGBABits = 0;
124-
if (desc.Format == DXGI_FORMAT_R8G8B8A8_UNORM || desc.Format == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB || desc.Format == DXGI_FORMAT_R8G8B8A8_UINT || desc.Format == DXGI_FORMAT_R8G8B8A8_TYPELESS) RGBABits = 8;
125-
if (desc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT || desc.Format == DXGI_FORMAT_R16G16B16A16_TYPELESS) RGBABits = 16;
126-
if (!RGBABits) return RET_ERROR_TEXTUREFORMAT;
123+
SharedImageMemory::EFormat Format;
124+
if (desc.Format == DXGI_FORMAT_R8G8B8A8_UNORM || desc.Format == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB || desc.Format == DXGI_FORMAT_R8G8B8A8_UINT || desc.Format == DXGI_FORMAT_R8G8B8A8_TYPELESS) Format = SharedImageMemory::FORMAT_UINT8;
125+
else if (desc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT || desc.Format == DXGI_FORMAT_R16G16B16A16_TYPELESS) Format = (IsLinearColorSpace ? SharedImageMemory::FORMAT_FP16_LINEAR : SharedImageMemory::FORMAT_FP16_GAMMA);
126+
else return RET_ERROR_TEXTUREFORMAT;
127127

128128
//Copy render texture to texture with CPU access and map the image data to RAM
129129
ctx->CopyResource(WriteTexture, d3dtex);
130130
D3D11_MAPPED_SUBRESOURCE mapResource;
131131
if (FAILED(ctx->Map(ReadTexture, 0, D3D11_MAP_READ, NULL, &mapResource))) return RET_ERROR_READTEXTURE;
132132

133133
//Push the captured data to the direct show filter
134-
SharedImageMemory::ESendResult res = c->Sender->Send(desc.Width, desc.Height, mapResource.RowPitch / (RGBABits / 2), RGBABits, ResizeMode, MirrorMode, (const unsigned char*)mapResource.pData);
134+
SharedImageMemory::ESendResult res = c->Sender->Send(desc.Width, desc.Height, mapResource.RowPitch / (Format == SharedImageMemory::FORMAT_UINT8 ? 4 : 8), mapResource.RowPitch * desc.Height, Format, ResizeMode, MirrorMode, (const unsigned char*)mapResource.pData);
135135

136136
ctx->Unmap(ReadTexture, 0);
137137

Source/shared.inl

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,12 @@ struct SharedImageMemory
4141
if (m_hSharedFile) CloseHandle(m_hSharedFile);
4242
}
4343

44+
enum EFormat { FORMAT_UINT8, FORMAT_FP16_GAMMA, FORMAT_FP16_LINEAR };
4445
enum EResizeMode { RESIZEMODE_DISABLED = 0, RESIZEMODE_LINEAR = 1 };
4546
enum EMirrorMode { MIRRORMODE_DISABLED = 0, MIRRORMODE_HORIZONTALLY = 1 };
4647
enum EReceiveResult { RECEIVERES_CAPTUREINACTIVE, RECEIVERES_NEWFRAME, RECEIVERES_OLDFRAME };
4748

48-
typedef void (*ReceiveCallbackFunc)(int width, int height, int stride, int rgbabits, EResizeMode resizemode, EMirrorMode mirrormode, uint8_t* buffer, void* callback_data);
49+
typedef void (*ReceiveCallbackFunc)(int width, int height, int stride, EFormat format, EResizeMode resizemode, EMirrorMode mirrormode, uint8_t* buffer, void* callback_data);
4950

5051
EReceiveResult Receive(ReceiveCallbackFunc callback, void* callback_data)
5152
{
@@ -55,7 +56,7 @@ struct SharedImageMemory
5556
bool IsNewFrame = (WaitForSingleObject(m_hSentFrameEvent, 200) == WAIT_OBJECT_0);
5657

5758
WaitForSingleObject(m_hMutex, INFINITE); //lock mutex
58-
callback(m_pSharedBuf->width, m_pSharedBuf->height, m_pSharedBuf->stride, m_pSharedBuf->rgbabits, (EResizeMode)m_pSharedBuf->resizemode, (EMirrorMode)m_pSharedBuf->mirrormode, m_pSharedBuf->data, callback_data);
59+
callback(m_pSharedBuf->width, m_pSharedBuf->height, m_pSharedBuf->stride, (EFormat)m_pSharedBuf->format, (EResizeMode)m_pSharedBuf->resizemode, (EMirrorMode)m_pSharedBuf->mirrormode, m_pSharedBuf->data, callback_data);
5960
ReleaseMutex(m_hMutex); //unlock mutex
6061

6162
return (IsNewFrame ? RECEIVERES_NEWFRAME : RECEIVERES_OLDFRAME);
@@ -67,19 +68,17 @@ struct SharedImageMemory
6768
}
6869

6970
enum ESendResult { SENDRES_TOOLARGE, SENDRES_WARN_FRAMESKIP, SENDRES_OK };
70-
ESendResult Send(int width, int height, int stride, int rgbabits, EResizeMode resizemode, EMirrorMode mirrormode, const uint8_t* buffer)
71+
ESendResult Send(int width, int height, int stride, DWORD DataSize, EFormat format, EResizeMode resizemode, EMirrorMode mirrormode, const uint8_t* buffer)
7172
{
7273
UCASSERT(buffer);
7374
UCASSERT(m_pSharedBuf);
74-
75-
DWORD DataSize = (DWORD)stride * (DWORD)height * 4 * (rgbabits / 8);
7675
if (m_pSharedBuf->maxSize < DataSize) return SENDRES_TOOLARGE;
7776

7877
WaitForSingleObject(m_hMutex, INFINITE); //lock mutex
7978
m_pSharedBuf->width = width;
8079
m_pSharedBuf->height = height;
8180
m_pSharedBuf->stride = stride;
82-
m_pSharedBuf->rgbabits = rgbabits;
81+
m_pSharedBuf->format = format;
8382
m_pSharedBuf->resizemode = resizemode;
8483
m_pSharedBuf->mirrormode = mirrormode;
8584
memcpy(m_pSharedBuf->data, buffer, DataSize);
@@ -142,7 +141,7 @@ private:
142141
int width;
143142
int height;
144143
int stride;
145-
int rgbabits;
144+
int format;
146145
int resizemode;
147146
int mirrormode;
148147
uint8_t data[1];
Binary file not shown.
Binary file not shown.

UnityCaptureSample/Assets/UnityCapture/UnityCapture.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ 3. This notice may not be removed or altered from any source distribution.
2929
public class UnityCapture : MonoBehaviour
3030
{
3131
enum ECaptureSendResult { SUCCESS = 0, WARNING_FRAMESKIP = 1, WARNING_CAPTUREINACTIVE = 2, ERROR_UNSUPPORTEDGRAPHICSDEVICE = 100, ERROR_PARAMETER = 101, ERROR_TOOLARGERESOLUTION = 102, ERROR_TEXTUREFORMAT = 103, ERROR_READTEXTURE = 104 };
32-
enum EResizeMode { DisabledShowMessage = 0, LinearResize = 1 }
32+
enum EResizeMode { Disabled = 0, LinearResize = 1 }
3333
enum EMirrorMode { Disabled = 0, MirrorHorizontally = 1 }
3434
[System.Runtime.InteropServices.DllImport("UnityCapturePlugin")] extern static System.IntPtr CaptureCreateInstance();
3535
[System.Runtime.InteropServices.DllImport("UnityCapturePlugin")] extern static void CaptureDeleteInstance(System.IntPtr instance);
36-
[System.Runtime.InteropServices.DllImport("UnityCapturePlugin")] extern static ECaptureSendResult CaptureSendTexture(System.IntPtr instance, System.IntPtr nativetexture, bool UseDoubleBuffering, EResizeMode ResizeMode, EMirrorMode MirrorMode);
36+
[System.Runtime.InteropServices.DllImport("UnityCapturePlugin")] extern static ECaptureSendResult CaptureSendTexture(System.IntPtr instance, System.IntPtr nativetexture, bool UseDoubleBuffering, EResizeMode ResizeMode, EMirrorMode MirrorMode, bool IsLinearColorSpace);
3737
System.IntPtr CaptureInstance;
3838

39-
[SerializeField] [Tooltip("Scale image if Unity and capture resolution don't match (can introduce frame dropping, not recommended)")] EResizeMode ResizeMode = EResizeMode.DisabledShowMessage;
39+
[SerializeField] [Tooltip("Scale image if Unity and capture resolution don't match (can introduce frame dropping, not recommended)")] EResizeMode ResizeMode = EResizeMode.Disabled;
4040
[SerializeField] [Tooltip("Mirror captured output image")] EMirrorMode MirrorMode = EMirrorMode.Disabled;
4141
[SerializeField] [Tooltip("Introduce a frame of latency in favor of frame rate")] bool DoubleBuffering = false;
4242
[SerializeField] [Tooltip("Check to enable VSync during capturing")] bool EnableVSync = false;
@@ -68,15 +68,15 @@ void OnDestroy()
6868
void OnRenderImage(RenderTexture source, RenderTexture destination)
6969
{
7070
Graphics.Blit(source, destination);
71-
switch (CaptureSendTexture(CaptureInstance, source.GetNativeTexturePtr(), DoubleBuffering, ResizeMode, MirrorMode))
71+
switch (CaptureSendTexture(CaptureInstance, source.GetNativeTexturePtr(), DoubleBuffering, ResizeMode, MirrorMode, QualitySettings.activeColorSpace == ColorSpace.Linear))
7272
{
7373
case ECaptureSendResult.SUCCESS: break;
7474
case ECaptureSendResult.WARNING_FRAMESKIP: if (!HideWarnings) Debug.LogWarning("[UnityCapture] Capture device did skip a frame read, capture frame rate will not match render frame rate."); break;
7575
case ECaptureSendResult.WARNING_CAPTUREINACTIVE: if (!HideWarnings) Debug.LogWarning("[UnityCapture] Capture device is inactive"); break;
7676
case ECaptureSendResult.ERROR_UNSUPPORTEDGRAPHICSDEVICE: Debug.LogError("[UnityCapture] Unsupported graphics device (only D3D11 supported)"); break;
7777
case ECaptureSendResult.ERROR_PARAMETER: Debug.LogError("[UnityCapture] Input parameter error"); break;
7878
case ECaptureSendResult.ERROR_TOOLARGERESOLUTION: Debug.LogError("[UnityCapture] Render resolution is too large to send to capture device"); break;
79-
case ECaptureSendResult.ERROR_TEXTUREFORMAT: Debug.LogError("[UnityCapture] Render texture format is unsupported (make sure the main camera has 'Allow HDR' set to off)"); break;
79+
case ECaptureSendResult.ERROR_TEXTUREFORMAT: Debug.LogError("[UnityCapture] Render texture format is unsupported (only basic non-HDR (ARGB32) and HDR (FP16/ARGB Half) formats are supported)"); break;
8080
case ECaptureSendResult.ERROR_READTEXTURE: Debug.LogError("[UnityCapture] Error while reading texture image data"); break;
8181
}
8282
}

0 commit comments

Comments
 (0)