Browse Source

libobs/graphics: Add NV12 texture support

jp9000 7 years ago
parent
commit
93fc61fa82

+ 25 - 0
libobs-d3d11/d3d11-stagesurf.cpp

@@ -41,3 +41,28 @@ gs_stage_surface::gs_stage_surface(gs_device_t *device, uint32_t width,
 	if (FAILED(hr))
 		throw HRError("Failed to create staging surface", hr);
 }
+
+gs_stage_surface::gs_stage_surface(gs_device_t *device, uint32_t width,
+		uint32_t height)
+	: gs_obj     (device, gs_type::gs_stage_surface),
+	  width      (width),
+	  height     (height),
+	  format     (GS_UNKNOWN),
+	  dxgiFormat (DXGI_FORMAT_NV12)
+{
+	HRESULT hr;
+
+	memset(&td, 0, sizeof(td));
+	td.Width            = width;
+	td.Height           = height;
+	td.MipLevels        = 1;
+	td.ArraySize        = 1;
+	td.Format           = dxgiFormat;
+	td.SampleDesc.Count = 1;
+	td.CPUAccessFlags   = D3D11_CPU_ACCESS_READ;
+	td.Usage            = D3D11_USAGE_STAGING;
+
+	hr = device->device->CreateTexture2D(&td, NULL, texture.Assign());
+	if (FAILED(hr))
+		throw HRError("Failed to create staging surface", hr);
+}

+ 67 - 1
libobs-d3d11/d3d11-subsystem.cpp

@@ -258,6 +258,17 @@ void gs_device::InitDevice(uint32_t adapterIdx)
 	if (FAILED(hr))
 		throw UnsupportedHWError("Failed to create device", hr);
 
+	ComQIPtr<ID3D11Device1> d3d11_1(device);
+	if (!!d3d11_1) {
+		D3D11_FEATURE_DATA_D3D11_OPTIONS opts = {};
+		hr = d3d11_1->CheckFeatureSupport(
+				D3D11_FEATURE_D3D11_OPTIONS,
+				&opts, sizeof(opts));
+		if (SUCCEEDED(hr)) {
+			nv12Supported = !!opts.ExtendedResourceSharing;
+		}
+	}
+
 	blog(LOG_INFO, "D3D11 loaded successfully, feature level used: %u",
 			(unsigned int)levelUsed);
 }
@@ -1315,7 +1326,7 @@ void device_stage_texture(gs_device_t *device, gs_stagesurf_t *dst,
 			throw "Source texture must be a 2D texture";
 		if (!dst)
 			throw "Destination surface is NULL";
-		if (dst->format != src->format)
+		if (dst->format != GS_UNKNOWN && dst->format != src->format)
 			throw "Source and destination formats do not match";
 		if (dst->width  != src2d->width ||
 		    dst->height != src2d->height)
@@ -2054,6 +2065,11 @@ extern "C" EXPORT bool device_shared_texture_available(void)
 	return true;
 }
 
+extern "C" EXPORT bool device_nv12_available(gs_device_t *device)
+{
+	return device->nv12Supported;
+}
+
 extern "C" EXPORT gs_texture_t *device_texture_create_gdi(gs_device_t *device,
 		uint32_t width, uint32_t height)
 {
@@ -2174,3 +2190,53 @@ extern "C" EXPORT int device_texture_release_sync(gs_texture_t *tex,
 	HRESULT hr = keyedMutex->ReleaseSync(key);
 	return hr == S_OK ? 0 : -1;
 }
+
+extern "C" EXPORT bool device_texture_create_nv12(gs_device_t *device,
+		gs_texture_t **p_tex_y, gs_texture_t **p_tex_uv,
+		uint32_t width, uint32_t height, uint32_t flags)
+{
+	if (!device->nv12Supported)
+		return false;
+
+	*p_tex_y = nullptr;
+	*p_tex_uv = nullptr;
+
+	gs_texture_2d *tex_y;
+	gs_texture_2d *tex_uv;
+
+	try {
+		tex_y = new gs_texture_2d(device, width, height, GS_R8, 1,
+				nullptr, flags, GS_TEXTURE_2D, false, true);
+		tex_uv = new gs_texture_2d(device, tex_y->texture, flags);
+
+	} catch (HRError error) {
+		blog(LOG_ERROR, "gs_texture_create_nv12 (D3D11): %s (%08lX)",
+				error.str, error.hr);
+		LogD3D11ErrorDetails(error, device);
+		return false;
+
+	} catch (const char *error) {
+		blog(LOG_ERROR, "gs_texture_create_nv12 (D3D11): %s", error);
+		return false;
+	}
+
+	*p_tex_y = tex_y;
+	*p_tex_uv = tex_uv;
+	return true;
+}
+
+extern "C" EXPORT gs_stagesurf_t *device_stagesurface_create_nv12(
+		gs_device_t *device, uint32_t width, uint32_t height)
+{
+	gs_stage_surface *surf = NULL;
+	try {
+		surf = new gs_stage_surface(device, width, height);
+	} catch (HRError error) {
+		blog(LOG_ERROR, "device_stagesurface_create (D3D11): %s "
+		                "(%08lX)",
+				error.str, error.hr);
+		LogD3D11ErrorDetails(error, device);
+	}
+
+	return surf;
+}

+ 13 - 2
libobs-d3d11/d3d11-subsystem.hpp

@@ -27,7 +27,7 @@
 #include <windows.h>
 #include <dxgi.h>
 #include <dxgi1_2.h>
-#include <d3d11.h>
+#include <d3d11_1.h>
 #include <d3dcompiler.h>
 
 #include <util/base.h>
@@ -80,6 +80,7 @@ static inline DXGI_FORMAT ConvertGSTextureFormat(gs_color_format format)
 	case GS_DXT1:        return DXGI_FORMAT_BC1_UNORM;
 	case GS_DXT3:        return DXGI_FORMAT_BC2_UNORM;
 	case GS_DXT5:        return DXGI_FORMAT_BC3_UNORM;
+	case GS_R8G8:        return DXGI_FORMAT_R8G8_UNORM;
 	}
 
 	return DXGI_FORMAT_UNKNOWN;
@@ -90,6 +91,7 @@ static inline gs_color_format ConvertDXGITextureFormat(DXGI_FORMAT format)
 	switch ((unsigned long)format) {
 	case DXGI_FORMAT_A8_UNORM:           return GS_A8;
 	case DXGI_FORMAT_R8_UNORM:           return GS_R8;
+	case DXGI_FORMAT_R8G8_UNORM:         return GS_R8G8;
 	case DXGI_FORMAT_R8G8B8A8_UNORM:     return GS_RGBA;
 	case DXGI_FORMAT_B8G8R8X8_UNORM:     return GS_BGRX;
 	case DXGI_FORMAT_B8G8R8A8_UNORM:     return GS_BGRA;
@@ -353,6 +355,10 @@ struct gs_texture_2d : gs_texture {
 	bool            genMipmaps = false;
 	uint32_t        sharedHandle = GS_INVALID_HANDLE;
 
+	gs_texture_2d   *pairedNV12texture = nullptr;
+	bool            nv12 = false;
+	bool            chroma = false;
+
 	vector<vector<uint8_t>> data;
 	vector<D3D11_SUBRESOURCE_DATA> srd;
 	D3D11_TEXTURE2D_DESC td = {};
@@ -383,8 +389,11 @@ struct gs_texture_2d : gs_texture {
 	gs_texture_2d(gs_device_t *device, uint32_t width, uint32_t height,
 			gs_color_format colorFormat, uint32_t levels,
 			const uint8_t **data, uint32_t flags,
-			gs_texture_type type, bool gdiCompatible);
+			gs_texture_type type, bool gdiCompatible,
+			bool nv12 = false);
 
+	gs_texture_2d(gs_device_t *device, ID3D11Texture2D *nv12,
+			uint32_t flags);
 	gs_texture_2d(gs_device_t *device, uint32_t handle);
 };
 
@@ -437,6 +446,7 @@ struct gs_stage_surface : gs_obj {
 
 	gs_stage_surface(gs_device_t *device, uint32_t width, uint32_t height,
 			gs_color_format colorFormat);
+	gs_stage_surface(gs_device_t *device, uint32_t width, uint32_t height);
 };
 
 struct gs_sampler_state : gs_obj {
@@ -780,6 +790,7 @@ struct gs_device {
 	ComPtr<ID3D11Device>        device;
 	ComPtr<ID3D11DeviceContext> context;
 	uint32_t                    adpIdx = 0;
+	bool                        nv12Supported = false;
 
 	gs_texture_2d               *curRenderTarget = nullptr;
 	gs_zstencil_buffer          *curZStencilBuffer = nullptr;

+ 37 - 4
libobs-d3d11/d3d11-texture2d.cpp

@@ -80,7 +80,7 @@ void gs_texture_2d::InitTexture(const uint8_t **data)
 	td.Height           = height;
 	td.MipLevels        = genMipmaps ? 0 : levels;
 	td.ArraySize        = type == GS_TEXTURE_CUBE ? 6 : 1;
-	td.Format           = dxgiFormat;
+	td.Format           = nv12 ? DXGI_FORMAT_NV12 : dxgiFormat;
 	td.BindFlags        = D3D11_BIND_SHADER_RESOURCE;
 	td.SampleDesc.Count = 1;
 	td.CPUAccessFlags   = isDynamic ? D3D11_CPU_ACCESS_WRITE : 0;
@@ -182,7 +182,12 @@ void gs_texture_2d::InitRenderTargets()
 {
 	HRESULT hr;
 	if (type == GS_TEXTURE_2D) {
-		hr = device->device->CreateRenderTargetView(texture, NULL,
+		D3D11_RENDER_TARGET_VIEW_DESC rtv;
+		rtv.Format = dxgiFormat;
+		rtv.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
+		rtv.Texture2D.MipSlice  = 0;
+
+		hr = device->device->CreateRenderTargetView(texture, &rtv,
 				renderTarget[0].Assign());
 		if (FAILED(hr))
 			throw HRError("Failed to create render target view",
@@ -210,7 +215,7 @@ void gs_texture_2d::InitRenderTargets()
 gs_texture_2d::gs_texture_2d(gs_device_t *device, uint32_t width,
 		uint32_t height, gs_color_format colorFormat, uint32_t levels,
 		const uint8_t **data, uint32_t flags_, gs_texture_type type,
-		bool gdiCompatible)
+		bool gdiCompatible, bool nv12_)
 	: gs_texture      (device, gs_type::gs_texture_2d, type, levels,
 	                   colorFormat),
 	  width           (width),
@@ -222,7 +227,8 @@ gs_texture_2d::gs_texture_2d(gs_device_t *device, uint32_t width,
 	  isDynamic       ((flags_ & GS_DYNAMIC) != 0),
 	  isShared        ((flags_ & SHARED_FLAGS) != 0),
 	  genMipmaps      ((flags_ & GS_BUILD_MIPMAPS) != 0),
-	  sharedHandle    (GS_INVALID_HANDLE)
+	  sharedHandle    (GS_INVALID_HANDLE),
+	  nv12            (nv12_)
 {
 	InitTexture(data);
 	InitResourceView();
@@ -231,6 +237,33 @@ gs_texture_2d::gs_texture_2d(gs_device_t *device, uint32_t width,
 		InitRenderTargets();
 }
 
+gs_texture_2d::gs_texture_2d(gs_device_t *device, ID3D11Texture2D *nv12tex,
+		uint32_t flags_)
+	: gs_texture      (device, gs_type::gs_texture_2d, GS_TEXTURE_2D),
+	  isRenderTarget  ((flags_ & GS_RENDER_TARGET) != 0),
+	  isDynamic       ((flags_ & GS_DYNAMIC) != 0),
+	  isShared        ((flags_ & SHARED_FLAGS) != 0),
+	  genMipmaps      ((flags_ & GS_BUILD_MIPMAPS) != 0),
+	  nv12            (true)
+{
+	texture = nv12tex;
+	texture->GetDesc(&td);
+
+	this->type       = GS_TEXTURE_2D;
+	this->format     = GS_R8G8;
+	this->flags      = flags_;
+	this->levels     = 1;
+	this->device     = device;
+	this->chroma     = true;
+	this->width      = td.Width / 2;
+	this->height     = td.Height / 2;
+	this->dxgiFormat = DXGI_FORMAT_R8G8_UNORM;
+
+	InitResourceView();
+	if (isRenderTarget)
+		InitRenderTargets();
+}
+
 gs_texture_2d::gs_texture_2d(gs_device_t *device, uint32_t handle)
 	: gs_texture      (device, gs_type::gs_texture_2d,
 	                   GS_TEXTURE_2D),

+ 4 - 0
libobs/graphics/graphics-imports.c

@@ -171,6 +171,8 @@ bool load_graphics_imports(struct gs_exports *exports, void *module,
 	GRAPHICS_IMPORT(gs_shader_set_default);
 	GRAPHICS_IMPORT(gs_shader_set_next_sampler);
 
+	GRAPHICS_IMPORT_OPTIONAL(device_nv12_available);
+
 	/* OSX/Cocoa specific functions */
 #ifdef __APPLE__
 	GRAPHICS_IMPORT_OPTIONAL(device_texture_create_from_iosurface);
@@ -192,6 +194,8 @@ bool load_graphics_imports(struct gs_exports *exports, void *module,
 	GRAPHICS_IMPORT_OPTIONAL(device_texture_get_shared_handle);
 	GRAPHICS_IMPORT_OPTIONAL(device_texture_acquire_sync);
 	GRAPHICS_IMPORT_OPTIONAL(device_texture_release_sync);
+	GRAPHICS_IMPORT_OPTIONAL(device_texture_create_nv12);
+	GRAPHICS_IMPORT_OPTIONAL(device_stagesurface_create_nv12);
 #endif
 
 	return success;

+ 8 - 0
libobs/graphics/graphics-internal.h

@@ -232,6 +232,8 @@ struct gs_exports {
 	void (*gs_shader_set_next_sampler)(gs_sparam_t *param,
 			gs_samplerstate_t *sampler);
 
+	bool (*device_nv12_available)(gs_device_t *device);
+
 #ifdef __APPLE__
 	/* OSX/Cocoa specific functions */
 	gs_texture_t *(*device_texture_create_from_iosurface)(gs_device_t *dev,
@@ -265,6 +267,12 @@ struct gs_exports {
 	int (*device_texture_acquire_sync)(gs_texture_t *tex, uint64_t key,
 			uint32_t ms);
 	int (*device_texture_release_sync)(gs_texture_t *tex, uint64_t key);
+	bool (*device_texture_create_nv12)(gs_device_t *device,
+			gs_texture_t **tex_y, gs_texture_t **tex_uv,
+			uint32_t width, uint32_t height, uint32_t flags);
+
+	gs_stagesurf_t *(*device_stagesurface_create_nv12)(gs_device_t *device,
+		uint32_t width, uint32_t height);
 #endif
 };
 

+ 72 - 0
libobs/graphics/graphics.c

@@ -2545,6 +2545,18 @@ enum gs_index_type gs_indexbuffer_get_type(const gs_indexbuffer_t *indexbuffer)
 	return thread_graphics->exports.gs_indexbuffer_get_type(indexbuffer);
 }
 
+bool gs_nv12_available(void)
+{
+	if (!gs_valid("gs_nv12_available"))
+		return false;
+
+	if (!thread_graphics->exports.device_nv12_available)
+		return false;
+
+	return thread_graphics->exports.device_nv12_available(
+			thread_graphics->device);
+}
+
 #ifdef __APPLE__
 
 /** Platform specific functions */
@@ -2726,4 +2738,64 @@ int gs_texture_release_sync(gs_texture_t *tex, uint64_t key)
 	return -1;
 }
 
+bool gs_texture_create_nv12(gs_texture_t **tex_y, gs_texture_t **tex_uv,
+		uint32_t width, uint32_t height, uint32_t flags)
+{
+	graphics_t *graphics = thread_graphics;
+	bool success = false;
+
+	if (!gs_valid("gs_texture_create_nv12"))
+		return false;
+
+	if ((width & 1) == 1 || (height & 1) == 1) {
+		blog(LOG_ERROR, "NV12 textures must have dimensions "
+				"divisible by 2.");
+		return false;
+	}
+
+	if (graphics->exports.device_texture_create_nv12) {
+		success = graphics->exports.device_texture_create_nv12(
+				graphics->device, tex_y, tex_uv,
+				width, height, flags);
+		if (success)
+			return true;
+	}
+
+	*tex_y = gs_texture_create(width, height, GS_R8, 1, NULL, flags);
+	*tex_uv = gs_texture_create(width / 2, height / 2, GS_R8G8, 1, NULL,
+			flags);
+
+	if (!*tex_y || !*tex_uv) {
+		if (*tex_y)
+			gs_texture_destroy(*tex_y);
+		if (*tex_uv)
+			gs_texture_destroy(*tex_uv);
+		*tex_y = NULL;
+		*tex_uv = NULL;
+		return false;
+	}
+
+	return true;
+}
+
+gs_stagesurf_t *gs_stagesurface_create_nv12(uint32_t width, uint32_t height)
+{
+	graphics_t *graphics = thread_graphics;
+
+	if (!gs_valid("gs_stagesurface_create_nv12"))
+		return NULL;
+
+	if ((width & 1) == 1 || (height & 1) == 1) {
+		blog(LOG_ERROR, "NV12 textures must have dimensions "
+				"divisible by 2.");
+		return NULL;
+	}
+
+	if (graphics->exports.device_stagesurface_create_nv12)
+		return graphics->exports.device_stagesurface_create_nv12(
+				graphics->device, width, height);
+
+	return NULL;
+}
+
 #endif

+ 11 - 1
libobs/graphics/graphics.h

@@ -71,7 +71,8 @@ enum gs_color_format {
 	GS_R32F,
 	GS_DXT1,
 	GS_DXT3,
-	GS_DXT5
+	GS_DXT5,
+	GS_R8G8,
 };
 
 enum gs_zstencil_format {
@@ -758,6 +759,8 @@ EXPORT size_t   gs_indexbuffer_get_num_indices(
 EXPORT enum gs_index_type gs_indexbuffer_get_type(
 		const gs_indexbuffer_t *indexbuffer);
 
+EXPORT bool     gs_nv12_available(void);
+
 #ifdef __APPLE__
 
 /** platform specific function for creating (GL_TEXTURE_RECTANGLE) textures
@@ -814,6 +817,12 @@ EXPORT int gs_texture_acquire_sync(gs_texture_t *tex, uint64_t key, uint32_t ms)
  */
 EXPORT int gs_texture_release_sync(gs_texture_t *tex, uint64_t key);
 
+EXPORT bool gs_texture_create_nv12(gs_texture_t **tex_y, gs_texture_t **tex_uv,
+		uint32_t width, uint32_t height, uint32_t flags);
+
+EXPORT gs_stagesurf_t *gs_stagesurface_create_nv12(
+		uint32_t width, uint32_t height);
+
 #endif
 
 /* inline functions used by modules */
@@ -838,6 +847,7 @@ static inline uint32_t gs_get_format_bpp(enum gs_color_format format)
 	case GS_DXT1:        return 4;
 	case GS_DXT3:        return 8;
 	case GS_DXT5:        return 8;
+	case GS_R8G8:        return 16;
 	case GS_UNKNOWN:     return 0;
 	}