forked from dolphin-emu/dolphin
		
	
		
			
				
	
	
		
			256 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			256 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Copyright 2013 Dolphin Emulator Project
 | |
| // Licensed under GPLv2
 | |
| // Refer to the license.txt file included.
 | |
| 
 | |
| #include "Globals.h"
 | |
| #include "GLUtil.h"
 | |
| #include "StreamBuffer.h"
 | |
| #include "MemoryUtil.h"
 | |
| #include "Render.h"
 | |
| #include "DriverDetails.h"
 | |
| #include "OnScreenDisplay.h"
 | |
| 
 | |
| namespace OGL
 | |
| {
 | |
| 
 | |
| static const u32 SYNC_POINTS = 16;
 | |
| static const u32 ALIGN_PINNED_MEMORY = 4096;
 | |
| 
 | |
| StreamBuffer::StreamBuffer(u32 type, size_t size, StreamType uploadType)
 | |
| : m_uploadtype(uploadType), m_buffertype(type), m_size(size)
 | |
| {
 | |
| 	glGenBuffers(1, &m_buffer);
 | |
| 	
 | |
| 	bool nvidia = !strcmp(g_ogl_config.gl_vendor, "NVIDIA Corporation");
 | |
| 	
 | |
| 	if(m_uploadtype & STREAM_DETECT)
 | |
| 	{
 | |
| 		// TODO: move this to InitBackendInfo
 | |
| 		if(g_ActiveConfig.bHackedBufferUpload && DriverDetails::HasBug(DriverDetails::BUG_BROKENHACKEDBUFFER))
 | |
| 		{
 | |
| 			OSD::AddMessage("Vertex Streaming Hack isn't supported by your GPU.", 10000);
 | |
| 			g_ActiveConfig.bHackedBufferUpload = false;
 | |
| 			g_Config.bHackedBufferUpload = false;
 | |
| 		}
 | |
| 		
 | |
| 		if(!g_ogl_config.bSupportsGLBaseVertex && (m_uploadtype & BUFFERDATA) 
 | |
| 			&& !DriverDetails::HasBug(DriverDetails::BUG_ISPOWERVR)
 | |
| 			&& !DriverDetails::HasBug(DriverDetails::BUG_ISTEGRA))
 | |
| 			m_uploadtype = BUFFERDATA;
 | |
| 		else if(!g_ogl_config.bSupportsGLBaseVertex && (m_uploadtype & BUFFERSUBDATA))
 | |
| 			m_uploadtype = BUFFERSUBDATA;
 | |
| 		else if(g_ogl_config.bSupportsGLSync && g_ActiveConfig.bHackedBufferUpload && (m_uploadtype & MAP_AND_RISK))
 | |
| 			m_uploadtype = MAP_AND_RISK;
 | |
| 		else if(g_ogl_config.bSupportsGLSync && g_ogl_config.bSupportsGLPinnedMemory && (!DriverDetails::HasBug(DriverDetails::BUG_BROKENPINNEDMEMORY) || type != GL_ELEMENT_ARRAY_BUFFER) && (m_uploadtype & PINNED_MEMORY))
 | |
| 			m_uploadtype = PINNED_MEMORY;
 | |
| 		else if(nvidia && (m_uploadtype & BUFFERSUBDATA))
 | |
| 			m_uploadtype = BUFFERSUBDATA;
 | |
| 		else if(g_ogl_config.bSupportsGLSync && (m_uploadtype & MAP_AND_SYNC))
 | |
| 			m_uploadtype = MAP_AND_SYNC;
 | |
| 		else 
 | |
| 			m_uploadtype = MAP_AND_ORPHAN;
 | |
| 	}
 | |
| 
 | |
| 	Init();
 | |
| }
 | |
| 
 | |
| StreamBuffer::~StreamBuffer()
 | |
| {
 | |
| 	Shutdown();
 | |
| 	glDeleteBuffers(1, &m_buffer);
 | |
| }
 | |
| 
 | |
| #define SLOT(x) (x)*SYNC_POINTS/m_size
 | |
| 
 | |
| void StreamBuffer::Alloc ( size_t size, u32 stride )
 | |
| {
 | |
| 	size_t m_iterator_aligned = m_iterator;
 | |
| 	if(m_iterator_aligned && stride) {
 | |
| 		m_iterator_aligned--;
 | |
| 		m_iterator_aligned = m_iterator_aligned - (m_iterator_aligned % stride) + stride;
 | |
| 	}
 | |
| 	size_t iter_end = m_iterator_aligned + size;
 | |
| 	
 | |
| 	switch(m_uploadtype) {
 | |
| 	case MAP_AND_ORPHAN:
 | |
| 		if(iter_end >= m_size) {
 | |
| 			glBufferData(m_buffertype, m_size, NULL, GL_STREAM_DRAW);
 | |
| 			m_iterator_aligned = 0;
 | |
| 		}
 | |
| 		break;
 | |
| 	case MAP_AND_SYNC:
 | |
| 	case PINNED_MEMORY:
 | |
| 		
 | |
| 		// insert waiting slots for used memory
 | |
| 		for(u32 i=SLOT(m_used_iterator); i<SLOT(m_iterator); i++)
 | |
| 		{
 | |
| 			fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
 | |
| 		}
 | |
| 		m_used_iterator = m_iterator;
 | |
| 		
 | |
| 		// wait for new slots to end of buffer
 | |
| 		for(u32 i=SLOT(m_free_iterator)+1; i<=SLOT(iter_end) && i < SYNC_POINTS; i++)
 | |
| 		{
 | |
| 			glClientWaitSync(fences[i], GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED);
 | |
| 			glDeleteSync(fences[i]);
 | |
| 		}
 | |
| 		m_free_iterator = iter_end;
 | |
| 		
 | |
| 		// if buffer is full
 | |
| 		if(iter_end >= m_size) {
 | |
| 			
 | |
| 			// insert waiting slots in unused space at the end of the buffer
 | |
| 			for(u32 i=SLOT(m_used_iterator); i < SYNC_POINTS; i++)
 | |
| 				fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
 | |
| 			
 | |
| 			// move to the start
 | |
| 			m_used_iterator = m_iterator_aligned = m_iterator = 0; // offset 0 is always aligned
 | |
| 			iter_end = size;
 | |
| 			
 | |
| 			// wait for space at the start
 | |
| 			for(u32 i=0; i<=SLOT(iter_end); i++)
 | |
| 			{
 | |
| 				glClientWaitSync(fences[i], GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED);
 | |
| 				glDeleteSync(fences[i]);
 | |
| 			}
 | |
| 			m_free_iterator = iter_end;
 | |
| 		}
 | |
| 		
 | |
| 		break;
 | |
| 	case MAP_AND_RISK:
 | |
| 		if(iter_end >= m_size) {
 | |
| 			m_iterator_aligned = 0;
 | |
| 		}
 | |
| 		break;
 | |
| 	case BUFFERSUBDATA:
 | |
| 	case BUFFERDATA:
 | |
| 		m_iterator_aligned = 0;
 | |
| 		break;
 | |
| 	case STREAM_DETECT:
 | |
| 	case DETECT_MASK: // Just to shutup warnings
 | |
| 		break;
 | |
| 	}
 | |
| 	m_iterator = m_iterator_aligned;
 | |
| }
 | |
| 
 | |
| size_t StreamBuffer::Upload ( u8* data, size_t size )
 | |
| {
 | |
| 	switch(m_uploadtype) {
 | |
| 	case MAP_AND_SYNC:
 | |
| 	case MAP_AND_ORPHAN:
 | |
| 		pointer = (u8*)glMapBufferRange(m_buffertype, m_iterator, size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
 | |
| 		if(pointer) {
 | |
| 			memcpy(pointer, data, size);
 | |
| 			glUnmapBuffer(m_buffertype);
 | |
| 		} else {
 | |
| 			ERROR_LOG(VIDEO, "Buffer mapping failed");
 | |
| 		}
 | |
| 		break;
 | |
| 	case PINNED_MEMORY:
 | |
| 	case MAP_AND_RISK:
 | |
| 		if(pointer)
 | |
| 			memcpy(pointer+m_iterator, data, size);
 | |
| 		break;
 | |
| 	case BUFFERSUBDATA:
 | |
| 		glBufferSubData(m_buffertype, m_iterator, size, data);
 | |
| 		break;
 | |
| 	case BUFFERDATA:
 | |
| 		glBufferData(m_buffertype, size, data, GL_STREAM_DRAW);
 | |
| 		break;
 | |
| 	case STREAM_DETECT:
 | |
| 	case DETECT_MASK: // Just to shutup warnings
 | |
| 		break;
 | |
| 	}
 | |
| 	size_t ret = m_iterator;
 | |
| 	m_iterator += size;
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| void StreamBuffer::Init()
 | |
| {
 | |
| 	m_iterator = 0;
 | |
| 	m_used_iterator = 0;
 | |
| 	m_free_iterator = 0;
 | |
| 	
 | |
| 	switch(m_uploadtype) {
 | |
| 	case MAP_AND_SYNC:
 | |
| 		fences = new GLsync[SYNC_POINTS];
 | |
| 		for(u32 i=0; i<SYNC_POINTS; i++)
 | |
| 			fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
 | |
| 		
 | |
| 	case MAP_AND_ORPHAN:
 | |
| 	case BUFFERSUBDATA:
 | |
| 		glBindBuffer(m_buffertype, m_buffer);
 | |
| 		glBufferData(m_buffertype, m_size, NULL, GL_STREAM_DRAW);
 | |
| 		break;
 | |
| 	case PINNED_MEMORY:
 | |
| 		glGetError(); // errors before this allocation should be ignored
 | |
| 		fences = new GLsync[SYNC_POINTS];
 | |
| 		for(u32 i=0; i<SYNC_POINTS; i++)
 | |
| 			fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
 | |
| 		
 | |
| 		pointer = (u8*)AllocateAlignedMemory(ROUND_UP(m_size,ALIGN_PINNED_MEMORY), ALIGN_PINNED_MEMORY );
 | |
| 		glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, m_buffer);
 | |
| 		glBufferData(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, ROUND_UP(m_size,ALIGN_PINNED_MEMORY), pointer, GL_STREAM_COPY);
 | |
| 		glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0);
 | |
| 		glBindBuffer(m_buffertype, m_buffer);
 | |
| 		
 | |
| 		// on error, switch to another backend. some old catalyst seems to have broken pinned memory support
 | |
| 		if(glGetError() != GL_NO_ERROR) {
 | |
| 			ERROR_LOG(VIDEO, "Pinned memory detected, but not working. Please report this: %s, %s, %s", g_ogl_config.gl_vendor, g_ogl_config.gl_renderer, g_ogl_config.gl_version);
 | |
| 			Shutdown();
 | |
| 			m_uploadtype = MAP_AND_SYNC;
 | |
| 			Init();
 | |
| 		}
 | |
| 		break;
 | |
| 	case MAP_AND_RISK:
 | |
| 		glBindBuffer(m_buffertype, m_buffer);
 | |
| 		glBufferData(m_buffertype, m_size, NULL, GL_STREAM_DRAW);
 | |
| 		pointer = (u8*)glMapBufferRange(m_buffertype, 0, m_size, GL_MAP_WRITE_BIT);
 | |
| 		glUnmapBuffer(m_buffertype);
 | |
| 		if(!pointer)
 | |
| 			ERROR_LOG(VIDEO, "Buffer allocation failed");
 | |
| 		break;
 | |
| 	case BUFFERDATA:
 | |
| 		glBindBuffer(m_buffertype, m_buffer);
 | |
| 		break;
 | |
| 	case STREAM_DETECT:
 | |
| 	case DETECT_MASK: // Just to shutup warnings
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void StreamBuffer::Shutdown()
 | |
| {
 | |
| 	switch(m_uploadtype) {
 | |
| 	case MAP_AND_SYNC:
 | |
| 		DeleteFences();
 | |
| 		break;
 | |
| 	case MAP_AND_RISK:
 | |
| 	case MAP_AND_ORPHAN:
 | |
| 	case BUFFERSUBDATA:
 | |
| 	case BUFFERDATA:
 | |
| 		break;
 | |
| 	case PINNED_MEMORY:
 | |
| 		DeleteFences();
 | |
| 		glBindBuffer(m_buffertype, 0);
 | |
| 		glFinish(); // ogl pipeline must be flushed, else this buffer can be in use
 | |
| 		FreeAlignedMemory(pointer);
 | |
| 		break;
 | |
| 	case STREAM_DETECT:
 | |
| 	case DETECT_MASK: // Just to shutup warnings
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void StreamBuffer::DeleteFences()
 | |
| {
 | |
| 	for(u32 i=SLOT(m_free_iterator)+1; i < SYNC_POINTS; i++)
 | |
| 		glDeleteSync(fences[i]);
 | |
| 	for(u32 i=0; i<SLOT(m_iterator); i++)
 | |
| 		glDeleteSync(fences[i]);
 | |
| 	delete [] fences;
 | |
| }
 | |
| 
 | |
| }
 |