194 lines
6.3 KiB
C++
194 lines
6.3 KiB
C++
/*
|
|
* Copyright (C) 2015 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "private/backend/CircularBuffer.h"
|
|
|
|
#include <utils/Log.h>
|
|
#include <utils/Panic.h>
|
|
#include <utils/architecture.h>
|
|
#include <utils/ashmem.h>
|
|
#include <utils/compiler.h>
|
|
#include <utils/debug.h>
|
|
#include <utils/ostream.h>
|
|
|
|
#if !defined(WIN32) && !defined(__EMSCRIPTEN__)
|
|
# include <sys/mman.h>
|
|
# include <unistd.h>
|
|
# define HAS_MMAP 1
|
|
#else
|
|
# define HAS_MMAP 0
|
|
#endif
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
using namespace utils;
|
|
|
|
namespace filament::backend {
|
|
|
|
size_t CircularBuffer::sPageSize = arch::getPageSize();
|
|
|
|
CircularBuffer::CircularBuffer(size_t size)
|
|
: mSize(size) {
|
|
mData = alloc(size);
|
|
mTail = mData;
|
|
mHead = mData;
|
|
}
|
|
|
|
CircularBuffer::~CircularBuffer() noexcept {
|
|
dealloc();
|
|
}
|
|
|
|
// If the system support mmap(), use it for creating a "hard circular buffer" where two virtual
|
|
// address ranges are mapped to the same physical pages.
|
|
//
|
|
// This code also has a soft circular buffer mode in case we're not able to allow sufficient
|
|
// continuous address spaces for both ranges.
|
|
//
|
|
// If the system does not support mmap, emulate soft circular buffer with two buffers next
|
|
// to each others and a special case in circularize()
|
|
|
|
UTILS_NOINLINE
|
|
void* CircularBuffer::alloc(size_t size) noexcept {
|
|
#if HAS_MMAP
|
|
void* data = nullptr;
|
|
void* vaddr = MAP_FAILED;
|
|
void* vaddr_shadow = MAP_FAILED;
|
|
void* vaddr_guard = MAP_FAILED;
|
|
size_t const BLOCK_SIZE = getBlockSize();
|
|
int const fd = ashmem_create_region("filament::CircularBuffer", size + BLOCK_SIZE);
|
|
if (fd >= 0) {
|
|
// reserve/find enough address space
|
|
void* reserve_vaddr = mmap(nullptr, size * 2 + BLOCK_SIZE,
|
|
PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
if (reserve_vaddr != MAP_FAILED) {
|
|
munmap(reserve_vaddr, size * 2 + BLOCK_SIZE);
|
|
// map the circular buffer once...
|
|
vaddr = mmap(reserve_vaddr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
|
if (vaddr != MAP_FAILED) {
|
|
// populate the address space with pages (because this is a circular buffer,
|
|
// all the pages will be allocated eventually, might as well do it now)
|
|
memset(vaddr, 0, size);
|
|
// and map the circular buffer again, behind the previous copy...
|
|
vaddr_shadow = mmap((char*)vaddr + size, size,
|
|
PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
|
if (vaddr_shadow != MAP_FAILED && (vaddr_shadow == (char*)vaddr + size)) {
|
|
// finally map the guard page, to make sure we never corrupt memory
|
|
vaddr_guard = mmap((char*)vaddr_shadow + size, BLOCK_SIZE, PROT_NONE,
|
|
MAP_PRIVATE, fd, (off_t)size);
|
|
if (vaddr_guard != MAP_FAILED && (vaddr_guard == (char*)vaddr_shadow + size)) {
|
|
// woo-hoo success!
|
|
mAshmemFd = fd;
|
|
data = vaddr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (UTILS_UNLIKELY(mAshmemFd < 0)) {
|
|
// ashmem failed
|
|
if (vaddr_guard != MAP_FAILED) {
|
|
munmap(vaddr_guard, BLOCK_SIZE);
|
|
}
|
|
|
|
if (vaddr_shadow != MAP_FAILED) {
|
|
munmap(vaddr_shadow, size);
|
|
}
|
|
|
|
if (vaddr != MAP_FAILED) {
|
|
munmap(vaddr, size);
|
|
}
|
|
|
|
if (fd >= 0) {
|
|
close(fd);
|
|
}
|
|
|
|
data = mmap(nullptr, size * 2 + BLOCK_SIZE,
|
|
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
|
|
FILAMENT_CHECK_POSTCONDITION(data != MAP_FAILED) <<
|
|
"couldn't allocate " << (size * 2 / 1024) <<
|
|
" KiB of virtual address space for the command buffer";
|
|
|
|
slog.w << "Using 'soft' CircularBuffer (" << (size * 2 / 1024) << " KiB)" << io::endl;
|
|
|
|
// guard page at the end
|
|
void* guard = (void*)(uintptr_t(data) + size * 2);
|
|
mprotect(guard, BLOCK_SIZE, PROT_NONE);
|
|
}
|
|
return data;
|
|
#else
|
|
return ::malloc(2 * size);
|
|
#endif
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
void CircularBuffer::dealloc() noexcept {
|
|
#if HAS_MMAP
|
|
if (mData) {
|
|
size_t const BLOCK_SIZE = getBlockSize();
|
|
munmap(mData, mSize * 2 + BLOCK_SIZE);
|
|
if (mAshmemFd >= 0) {
|
|
close(mAshmemFd);
|
|
mAshmemFd = -1;
|
|
}
|
|
}
|
|
#else
|
|
::free(mData);
|
|
#endif
|
|
mData = nullptr;
|
|
}
|
|
|
|
|
|
CircularBuffer::Range CircularBuffer::getBuffer() noexcept {
|
|
Range const range{ .tail = mTail, .head = mHead };
|
|
|
|
char* const pData = static_cast<char*>(mData);
|
|
char const* const pEnd = pData + mSize;
|
|
char const* const pHead = static_cast<char const*>(mHead);
|
|
if (UTILS_UNLIKELY(pHead >= pEnd)) {
|
|
size_t const overflow = pHead - pEnd;
|
|
if (UTILS_LIKELY(mAshmemFd > 0)) {
|
|
assert_invariant(overflow <= mSize);
|
|
mHead = static_cast<void*>(pData + overflow);
|
|
// Data Tail End Head [virtual]
|
|
// v v v v
|
|
// +-------------:----+-----:--------------+
|
|
// | : | : |
|
|
// +-----:------------+--------------------+
|
|
// Head |<------ copy ------>| [physical]
|
|
} else {
|
|
// Data Tail End Head
|
|
// v v v v
|
|
// +-------------:----+-----:--------------+
|
|
// | : | : |
|
|
// +-----|------------+-----|--------------+
|
|
// |<---------------->|
|
|
// sliding window
|
|
mHead = mData;
|
|
}
|
|
}
|
|
mTail = mHead;
|
|
|
|
return range;
|
|
}
|
|
|
|
} // namespace filament::backend
|