Commits

spencercw committed a9c05ba

Fall back to SDL as appropriate if WASAPI or Direct3D 11 is not available at runtime.

Comments (0)

Files changed (9)

gb_emulator/include/gb_emulator/gb_sound.hpp

 	//! Destructor.
 	virtual ~GbSound();
 
+	//! Indicates whether the audio driver is available.
+	/**
+	 * This function does a runtime check to see whether the audio driver can be used.
+	 * \return Whether the audio driver is available and may be used.
+	 */
+	virtual bool isAvailable() = 0;
+
+	//! Performs any initialisation that needs to be carried out before processing can start.
+	virtual void initialise() = 0;
+
 	//! Resets the sound emulator to its default state.
 	void reset();
 

gb_emulator/include/gb_emulator/gb_sound_sdl.h

 	//! Constructor; opens the sound device.
 	GbSoundSdl(Gb &gb);
 
+	//! Indicates whether the audio driver is available.
+	/**
+	 * The implementation for this class always returns \c true.
+	 * \return \c true.
+	 */
+	bool isAvailable()
+	{
+		return true;
+	}
+
+	//! Performs initialisation.
+	/**
+	 * The implementation for this class does nothing.
+	 */
+	void initialise()
+	{
+	}
+
 	//! Generates a single sample.
 	double poll();
 

gb_emulator/include/gb_emulator/gb_sound_wasapi.h

 class GbSoundWasapi: public GbSound
 {
 public:
-	//! Constructor; opens the sound device.
+	//! Constructor.
 	GbSoundWasapi(Gb &gb);
 
 	//! Destructor.
 	~GbSoundWasapi();
 
+	//! Indicates whether WASAPI is available.
+	/**
+	 * This function does a runtime check to see whether WASAPI can be used on this system. If not
+	 * then none of the other functions in this class may be used.
+	 * \return Whether WASAPI is available.
+	 */
+	bool isAvailable();
+
+	//! Opens the sound device and prepares for playback.
+	void initialise();
+
 	//! Generates a single sample.
 	double poll();
 

gb_emulator/include/gb_emulator/gb_video.hpp

 	//! Destructor.
 	virtual ~GbVideo();
 
+	//! Indicates whether the video driver is available.
+	/**
+	 * This function does a runtime check to see whether the video driver can be used.
+	 * \return Whether the video driver is available and may be used.
+	 */
+	virtual bool isAvailable() = 0;
+
+	//! Performs any initialisation that needs to be carried out before processing can start.
+	virtual void initialise() = 0;
+
 	//! Resets the video emulator to its default state.
 	void reset();
 

gb_emulator/include/gb_emulator/gb_video_d3d11.h

 class GbVideoD3D11: public GbVideo
 {
 public:
-	//! Constructor; initialises Direct3D and opens the display.
+	//! Constructor.
 	/**
 	 * \param gb The emulator context.
 	 * \param renderer The renderer to use. If set to \c GbConfig::RENDER_HARDWARE then colour
 	//! Destructor.
 	~GbVideoD3D11();
 
+	//! Indicates whether Direct3D 11 is available.
+	/**
+	 * This function does a runtime check to see whether Direct3D 11 can be used on this system. If
+	 * not then none of the other functions in this class may be used.
+	 * \return Whether Direct3D 11 is available.
+	 */
+	bool isAvailable();
+
+	//! Initialises Direct3D and opens the display.
+	void initialise();
+
 private:
 	// Direct3D stuff
 	boost::shared_ptr<HINSTANCE__> d3d11_;
 	// The rendering method
 	GbConfig::Renderer renderer_;
 
+	// The installation directory
+	boost::filesystem::path installDir_;
+
 	// Indicates which pixel shader is bound. There are separate shaders for GBC and DMG mode
 	// because they have different colour formats. This is only used when hardware rendering is
 	// used.
 
 	// Initialisation functions
 	void createRenderTarget();
-	void initPipeline(const boost::filesystem::path &installDir);
+	void initPipeline();
 	void initGraphics();
 
 	// Redraws the display

gb_emulator/include/gb_emulator/gb_video_sdl.h

 	 */
 	GbVideoSdl(Gb &gb, GbConfig::Renderer renderer);
 
+
+	//! Indicates whether the video driver is available.
+	/**
+	 * The implementation for this class always returns \c true.
+	 * \return \c true.
+	 */
+	bool isAvailable()
+	{
+		return true;
+	}
+
+	//! Performs initialisation.
+	/**
+	 * The implementation for this class does nothing.
+	 */
+	void initialise()
+	{
+	}
+
 private:
 	SDL_Surface *surface_;
 

gb_emulator/src/gb.cpp

 	case GbConfig::AUDIO_WASAPI:
 		clog << "loading WASAPI audio driver\n";
 		sound_.reset(new GbSoundWasapi(*this));
-		break;
+		if (sound_->isAvailable())
+		{
+			break;
+		}
+		else
+		{
+			// WASAPI is not available; fall through to SDL
+			clog << "WASAPI is not available on this system...\n";
+		}
 
 	case GbConfig::AUDIO_SDL:
 		clog << "loading SDL audio driver\n";
 	default:
 		assert(!"unknown audio driver");
 	}
+	sound_->initialise();
 
 	// Video
 	switch (config.videoDriver)
 	case GbConfig::VIDEO_D3D11:
 		clog << "loading Direct3D 11 video driver\n";
 		video_.reset(new GbVideoD3D11(*this, config.renderer, installDir_));
-		break;
+		if (video_->isAvailable())
+		{
+			break;
+		}
+		else
+		{
+			// Direct3D 11 is not available; fall through to SDL
+			clog << "Direct3D 11 is not available on this system...\n";
+		}
 
 	case GbConfig::VIDEO_SDL:
 		clog << "loading SDL video driver\n";
 	default:
 		assert(!"unknown video driver");
 	}
+	video_->initialise();
 
 	// Memory
 	mem_.reset(new GbMemoryImpl(*this));

gb_emulator/src/gb_sound_wasapi.cpp

 	{
 		throw runtime_error("failed to initialise COM: " + lexical_cast<string>(hr));
 	}
+}
 
+GbSoundWasapi::~GbSoundWasapi()
+{
+	// Uninitialise COM
+	CoUninitialize();
+}
+
+bool GbSoundWasapi::isAvailable()
+{
+	// Attempt to get an instance of MMDeviceEnumerator; if it fails then WASAPI is not available
+	CComPtr<IMMDeviceEnumerator> devices;
+	HRESULT hr = devices.CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER);
+	return SUCCEEDED(hr);
+}
+
+void GbSoundWasapi::initialise()
+{
 	// Get the audio endpoint
 	CComPtr<IMMDeviceEnumerator> devices;
-	hr = devices.CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER);
+	HRESULT hr = devices.CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER);
 	if (FAILED(hr))
 	{
 		throw runtime_error("failed to get audio device enumerator: " + lexical_cast<string>(hr));
 	renderer_->initialise(device, boost::bind(&GbSoundWasapi::saveSampleRate, this, _1));
 }
 
-GbSoundWasapi::~GbSoundWasapi()
-{
-	// Uninitialise COM
-	CoUninitialize();
-}
-
 double GbSoundWasapi::poll()
 {
 	// Generate the sample

gb_emulator/src/gb_video_d3d11.cpp

 using std::runtime_error;
 using std::string;
 
+typedef HRESULT (WINAPI *D3D11CreateDevice_t)(IDXGIAdapter *pAdapter, D3D_DRIVER_TYPE DriverType,
+	HMODULE Software, UINT Flags, const D3D_FEATURE_LEVEL *pFeatureLevels, UINT FeatureLevels,
+	UINT SDKVersion, ID3D11Device **ppDevice, D3D_FEATURE_LEVEL *pFeatureLevel,
+	ID3D11DeviceContext **ppImmediateContext);
 typedef HRESULT (WINAPI *D3D11CreateDeviceAndSwapChain_t)(IDXGIAdapter *pAdapter,
 	D3D_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags,
 	const D3D_FEATURE_LEVEL *pFeatureLevels, UINT FeatureLevels, UINT SDKVersion,
 	FLOAT v;
 };
 
+static const D3D_FEATURE_LEVEL SUPPORTED_FEATURE_LEVELS[] = {
+	D3D_FEATURE_LEVEL_11_0,
+	D3D_FEATURE_LEVEL_10_1,
+	D3D_FEATURE_LEVEL_10_0 };
 static GbVideoD3D11 *gD3D11;
 
 GbVideoD3D11::GbVideoD3D11(Gb &gb, GbConfig::Renderer renderer, const fs::path &installDir):
 GbVideo(gb),
+installDir_(installDir),
 gbcBound_(true)
 {
 	// Load the Direct3D 11 library if available
 	d3d11_.reset(LoadLibraryW(L"d3d11.dll"), FreeLibrary);
+
+	// Check the rendering method
+	if (renderer != GbConfig::RENDER_DEFAULT && renderer != GbConfig::RENDER_SOFTWARE &&
+		renderer != GbConfig::RENDER_HARDWARE)
+	{
+		throw logic_error("unsupported rendering method");
+	}
+	renderer_ = renderer == GbConfig::RENDER_DEFAULT ? GbConfig::RENDER_SOFTWARE : renderer;
+}
+
+bool GbVideoD3D11::isAvailable()
+{
+	if (!d3d11_)
+	{
+		return false;
+	}
+
+	// Direct3D 11 is installed, but not necessarily supported by the hardware; try opening the
+	// device
+	D3D11CreateDevice_t D3D11CreateDevice_ = reinterpret_cast<D3D11CreateDevice_t>(
+		GetProcAddress(d3d11_.get(), "D3D11CreateDevice"));
+	if (!D3D11CreateDevice_)
+	{
+		return false;
+	}
+
+	ATL::CComPtr<ID3D11Device> device;
+	ATL::CComPtr<ID3D11DeviceContext> deviceContext;
+	D3D_FEATURE_LEVEL featureLevel;
+	HRESULT hr = D3D11CreateDevice_(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL,
+		D3D11_CREATE_DEVICE_SINGLETHREADED, SUPPORTED_FEATURE_LEVELS,
+		ARRAY_SIZE(SUPPORTED_FEATURE_LEVELS), D3D11_SDK_VERSION, &device, &featureLevel, &deviceContext);
+	return SUCCEEDED(hr);
+}
+
+void GbVideoD3D11::initialise()
+{
 	if (!d3d11_)
 	{
 		throw runtime_error("Direct3D 11 not available");
 	// Save a reference to this for use in the window procedure
 	gD3D11 = this;
 
-	// Check the rendering method
-	if (renderer != GbConfig::RENDER_DEFAULT && renderer != GbConfig::RENDER_SOFTWARE &&
-		renderer != GbConfig::RENDER_HARDWARE)
-	{
-		throw logic_error("unsupported rendering method");
-	}
-	renderer_ = renderer == GbConfig::RENDER_DEFAULT ? GbConfig::RENDER_SOFTWARE : renderer;
-
 	if (renderer_ == GbConfig::RENDER_SOFTWARE)
 	{
 		unscaledPixelBuffer_.reset(new uint32_t[WIDTH * HEIGHT]);
 	swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
 	swapChainDesc.Flags = 0;
 
-	static const D3D_FEATURE_LEVEL SUPPORTED_FEATURE_LEVELS[] = {
-		D3D_FEATURE_LEVEL_11_0,
-		D3D_FEATURE_LEVEL_10_1,
-		D3D_FEATURE_LEVEL_10_0
-	};
 	D3D_FEATURE_LEVEL featureLevel;
 	HRESULT hr = D3D11CreateDeviceAndSwapChain_(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL,
 		D3D11_CREATE_DEVICE_SINGLETHREADED, SUPPORTED_FEATURE_LEVELS,
 	deviceContext_->RSSetViewports(1, &viewport);
 
 	// Perform remaining initialisation
-	initPipeline(installDir);
+	initPipeline();
 	initGraphics();
 }
 
 	deviceContext_->OMSetRenderTargets(1, &renderTarget_.p, NULL);
 }
 
-void GbVideoD3D11::initPipeline(const fs::path &installDir)
+void GbVideoD3D11::initPipeline()
 {
 	// Initialise Cg
 	cgContext_.reset(cgCreateContext(), cgDestroyContext);
 	}
 
 	// Compile the shaders
-	fs::path vertexShaderPath = installDir / L"shaders/vertex_shader.cg";
-	fs::path pixelShaderPath  = installDir / L"shaders/pixel_shader.cg";
+	fs::path vertexShaderPath = installDir_ / L"shaders/vertex_shader.cg";
+	fs::path pixelShaderPath  = installDir_ / L"shaders/pixel_shader.cg";
 	vertexShaderPath.make_preferred();
 	pixelShaderPath.make_preferred();