Refactor rdcstr with small-string and literal-string optimisations

* rdcstr no longer inherits from rdcarray, it implements all functionality
  itself.
* There are now three representations:
  - Heap-allocated, same as how rdcarray behaves.
  - Local-allocated, for small strings we store them in a union array.
  - Compile-time literal, only created from user-defined literals.
* The main observation is that a lot of RenderDoc's strings are compile-time
  literals either from struct names, member names, or stringified enum values.
  Storing these directly and allowing them to be moved and copied quickly saves
  on allocations and time. When the string is modified, it's copied to one of the other formats.
This commit is contained in:
baldurk committed 2019-05-15 14:12:17 +01:00
1 parent fb333ebc9b
commit c8a518f05c
7 files changed
+1275 -124

No files matched your search

+1 -1
View File
@@ -2805,7 +2805,7 @@ void TextureViewer::OnEventChanged(uint32_t eventId)
QString bindName = (copy || clear) ? tr("Destination") : QString();
QString slotName = (copy || clear)
? tr("DST")
: (m_Ctx.CurPipelineState().OutputAbbrev() + QString::number(rt));
: QString(m_Ctx.CurPipelineState().OutputAbbrev() + QString::number(rt));
InitResourcePreview(prev, RTs[rt].resourceId, RTs[rt].typeHint, false, follow, bindName,
slotName);
+1 -51
View File
@@ -767,57 +767,7 @@ public:
#endif
};
DOCUMENT("");
struct rdcstr : public rdcarray<char>
{
// extra string constructors
rdcstr() : rdcarray<char>() {}
rdcstr(const rdcstr &in) : rdcarray<char>() { assign(in); }
rdcstr(const std::string &in) : rdcarray<char>() { assign(in.c_str(), in.size()); }
rdcstr(const char *const in) : rdcarray<char>() { assign(in, strlen(in)); }
// extra string assignment
rdcstr &operator=(const std::string &in)
{
assign(in.c_str(), in.size());
return *this;
}
rdcstr &operator=(const char *const in)
{
assign(in, strlen(in));
return *this;
}
// cast operators
operator std::string() const { return std::string(elems, elems + usedCount); }
#if defined(RENDERDOC_QT_COMPAT)
rdcstr(const QString &in) : rdcarray<char>()
{
QByteArray arr = in.toUtf8();
assign(arr.data(), (size_t)arr.size());
}
operator QString() const { return QString::fromUtf8(elems, (int32_t)usedCount); }
operator QVariant() const { return QVariant(QString::fromUtf8(elems, (int32_t)usedCount)); }
#endif
// conventional data accessor
DOCUMENT("");
const char *c_str() const { return elems ? elems : ""; }
// equality checks
bool operator==(const char *const o) const
{
if(!elems)
return o == NULL;
return !strcmp(elems, o);
}
bool operator==(const std::string &o) const { return o == elems; }
bool operator==(const rdcstr &o) const { return *this == (const char *)o.elems; }
bool operator!=(const char *const o) const { return !(*this == o); }
bool operator!=(const std::string &o) const { return !(*this == o); }
bool operator!=(const rdcstr &o) const { return !(*this == o); }
// define ordering operators
bool operator<(const rdcstr &o) const { return strcmp(elems, o.elems) < 0; }
bool operator>(const rdcstr &o) const { return strcmp(elems, o.elems) > 0; }
};
#include "rdcstr.h"
DOCUMENT("");
struct bytebuf : public rdcarray<byte>
+779
View File
@@ -0,0 +1,779 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#pragma once
#include <ostream>
#include <string>
// special type for storing literals. This allows functions to force callers to pass them literals
DOCUMENT("");
class rdcliteral
{
const char *str;
size_t len;
// make the literal operator a friend so it can construct fixed strings. No-one else can.
friend rdcliteral operator"" _lit(const char *str, size_t len);
rdcliteral(const char *s, size_t l) : str(s), len(l) {}
rdcliteral() = delete;
public:
const char *c_str() const { return str; }
size_t length() const { return len; }
};
inline rdcliteral operator"" _lit(const char *str, size_t len)
{
return rdcliteral(str, len);
}
DOCUMENT("");
class rdcstr
{
private:
// ARRAY_STATE is deliberately 0 so that 0-initialisation is a valid empty array string
static constexpr size_t ARRAY_STATE = size_t(0);
static constexpr size_t ALLOC_STATE = size_t(1) << ((sizeof(size_t) * 8) - 2);
static constexpr size_t FIXED_STATE = size_t(1) << ((sizeof(size_t) * 8) - 1);
struct alloc_ptr_rep
{
// we reserve two bits but we only have three states
static constexpr size_t CAPACITY_MASK = (~size_t(0)) >> 2;
static constexpr size_t STATE_MASK = ~CAPACITY_MASK;
// the storage
char *str;
// the current size of the string (less than or equal to capacity). Doesn't include NULL
// terminator
size_t size;
// accessors for capacity, preserving the state bits
size_t get_capacity() const { return _capacity & CAPACITY_MASK; };
void set_capacity(size_t s) { _capacity = ALLOC_STATE | s; }
private:
// the capacity currently available in the allocated storage. Doesn't include NULL terminator
size_t _capacity;
};
struct fixed_ptr_rep
{
// the immutable string storage
const char *str;
// the size of the immutable string. Doesn't include NULL terminator
size_t size;
// access to the flags
size_t flags;
};
struct arr_rep
{
// all bytes except the last one are used for storing short strings
char str[sizeof(size_t) * 3 - 1];
// capacity is fixed - 1 less than the number of characters above (so we always have room for
// the NULL terminator)
static const size_t capacity = sizeof(arr_rep::str) - 1;
// don't have to mask any state bits here because we assume the size is in bounds and state bits
// of 0 means array representation, so setting and retrieving can return the size as-is.
// We keep these accessors though just in case that changes in future
size_t get_size() const { return _size; }
void set_size(size_t s) { _size = (unsigned char)s; }
private:
// we only have 6-bits of this available is enough for up to 63 size, more than what we can
// store anyway.
unsigned char _size;
};
// zero-initialised this becomes an empty string in array format
union string_data
{
// stored as size, capacity, and pointer to d
alloc_ptr_rep alloc;
// stored as size and pointer
fixed_ptr_rep fixed;
// stored as in-line array
arr_rep arr;
} d;
bool is_alloc() const { return !!(d.fixed.flags & ALLOC_STATE); }
bool is_fixed() const { return !!(d.fixed.flags & FIXED_STATE); }
bool is_array() const { return !is_alloc() && !is_fixed(); }
/////////////////////////////////////////////////////////////////
// memory management, in a dll safe way
DOCUMENT("");
static char *allocate(size_t count)
{
#ifdef RENDERDOC_EXPORTS
return (char *)malloc(count);
#else
return (char *)RENDERDOC_AllocArrayMem(count);
#endif
}
static void deallocate(const char *p)
{
#ifdef RENDERDOC_EXPORTS
free((void *)p);
#else
RENDERDOC_FreeArrayMem((const void *)p);
#endif
}
// if we're not already mutable (i.e. fixed string) then change to a mutable string
void ensure_mutable(size_t s = 0)
{
if(!is_fixed())
return;
// if we're not yet mutable, convert to allocated string at the same time as reserving as
// necessary
const char *fixed_str = d.fixed.str;
size_t fixed_size = d.fixed.size;
// allocate at least enough for the string - reserve is non-destructive.
if(s < fixed_size)
s = fixed_size;
// if we can satisfy the request with the array representation, it's easier
if(s <= d.arr.capacity)
{
// copy d, we can safely include the NULL terminator we know is present
memcpy(d.arr.str, fixed_str, fixed_size + 1);
// store metadata
d.arr.set_size(fixed_size);
}
else
{
// otherwise we need to allocate
// allocate the requested size now, +1 for NULL terminator
d.alloc.str = allocate(s + 1);
// copy d, we can safely include the NULL terminator we know is present
memcpy(d.alloc.str, fixed_str, fixed_size + 1);
// store metadata
d.alloc.set_capacity(fixed_size);
d.alloc.size = fixed_size;
}
}
public:
// default constructor just 0-initialises
rdcstr() { memset(&d, 0, sizeof(d)); }
~rdcstr()
{
// only free d if it was allocated
if(is_alloc())
deallocate(d.alloc.str);
}
// move constructor is simple - just move the d element. We take ownership of the allocation if
// it's allocated, otherwise this is a copy anyway
rdcstr(rdcstr &&in)
{
// we can just move the d element
d = in.d;
// the input no longer owns d. Set to 0 to be extra-clear
memset(&in.d, 0, sizeof(d));
}
rdcstr &operator=(rdcstr &&in)
{
// deallocate current storage if it's allocated
if(is_alloc())
deallocate(d.alloc.str);
// move the d element
d = in.d;
// the input no longer owns d. Set to 0 to be extra-clear
memset(&in.d, 0, sizeof(d));
return *this;
}
// special constructor from literals
rdcstr(const rdcliteral &lit)
{
d.fixed.str = lit.c_str();
d.fixed.size = lit.length();
d.fixed.flags = FIXED_STATE;
}
// copy constructors forward to assign
rdcstr(const rdcstr &in)
{
memset(&d, 0, sizeof(d));
assign(in);
}
rdcstr(const std::string &in)
{
memset(&d, 0, sizeof(d));
assign(in.c_str(), in.size());
}
rdcstr(const char *const in)
{
memset(&d, 0, sizeof(d));
assign(in, strlen(in));
}
rdcstr(const char *const in, size_t length)
{
memset(&d, 0, sizeof(d));
assign(in, length);
}
// also operator=
rdcstr &operator=(const rdcstr &in)
{
assign(in);
return *this;
}
rdcstr &operator=(const std::string &in)
{
assign(in.c_str(), in.size());
return *this;
}
rdcstr &operator=(const char *const in)
{
assign(in, strlen(in));
return *this;
}
// assign from an rdcstr, copy the d element and allocate if needed
void assign(const rdcstr &in)
{
// if the input d is allocated, we need to make our own allocation. Go through the standard
// string assignment function which will allocate & copy
if(in.is_alloc())
{
assign(in.d.alloc.str, in.d.alloc.size);
}
else
{
// otherwise just deallocate if necessary and copy
if(is_alloc())
deallocate(d.alloc.str);
d = in.d;
}
}
// assign from something else
void assign(const char *const in, size_t length)
{
// ensure we have enough capacity allocated
reserve(length);
// write to the string we're using, depending on if we allocated or not
char *str = is_alloc() ? d.alloc.str : d.arr.str;
// copy the string itself
memcpy(str, in, length);
// cap off with NULL terminator
str[length] = 0;
if(is_alloc())
d.alloc.size = length;
else
d.arr.set_size(length);
}
void assign(const char *const str) { assign(str, strlen(str)); }
// in-place modification functions
void append(const char *const str) { append(str, strlen(str)); }
void append(const std::string &str) { append(str.c_str(), str.size()); }
void append(const rdcstr &str) { append(str.c_str(), str.size()); }
void append(const char *const str, size_t length) { insert(size(), str, length); }
void erase(size_t offs, size_t count = 1)
{
const size_t sz = size();
// invalid offset
if(offs >= sz)
return;
if(count > sz - offs)
count = sz - offs;
char *str = data();
for(size_t i = offs; i < sz - count; i++)
str[i] = str[i + count];
resize(sz - count);
}
void insert(size_t offset, const char *const str) { insert(offset, str, strlen(str)); }
void insert(size_t offset, const std::string &str) { insert(offset, str.c_str(), str.size()); }
void insert(size_t offset, const rdcstr &str) { insert(offset, str.c_str(), str.size()); }
void insert(size_t offset, const char *const instr, size_t length)
{
const size_t sz = size();
// invalid offset
if(offset > sz)
return;
// allocate needed size
reserve(sz + length);
// move anything after the offset upwards, including the NULL terminator by starting at sz + 1
char *str = data();
for(size_t i = sz + 1; i > offset; i--)
str[i + length - 1] = str[i - 1];
// copy the string to the offset
memcpy(str + offset, instr, length);
// increase the length
if(is_alloc())
d.alloc.size += length;
else
d.arr.set_size(sz + length);
}
// cast operators
operator std::string() const
{
const char *s = c_str();
return std::string(s, s + size());
}
// read-only by-value accessor can look up directly in c_str() since it can't be modified
char operator[](size_t i) const { return c_str()[i]; }
// assignment operator must make the string mutable first
char &operator[](size_t i)
{
ensure_mutable();
return is_alloc() ? d.alloc.str[i] : d.arr.str[i];
}
// stl type interface
void reserve(size_t s)
{
if(is_fixed())
{
ensure_mutable(s);
return;
}
const size_t old_capacity = capacity();
// nothing to do if we already have this much space. We only size up
if(s <= old_capacity)
return;
// if we're currently using the array representation, the current capacity is always maxed out,
// meaning if we don't have enough space we *must* now allocate.
const size_t old_size = is_alloc() ? d.alloc.size : d.arr.get_size();
const char *old_str = is_alloc() ? d.alloc.str : d.arr.str;
// either double, or allocate what's needed, whichever is bigger. ie. by default we double in
// size but we don't grow exponentially in 2^n to cover a single really large resize
if(old_capacity * 2 > s)
s = old_capacity * 2;
// allocate +1 for the NULL terminator
char *new_str = allocate(s + 1);
// copy the current characters over, including NULL terminator
memcpy(new_str, old_str, old_size + 1);
// deallocate the old storage
if(is_alloc())
deallocate(d.alloc.str);
// we are now an allocated string
d.alloc.str = new_str;
// updated capacity
d.alloc.set_capacity(s);
// size is unchanged
d.alloc.size = old_size;
}
void push_back(char c)
{
// store old size
size_t s = size();
// reserve enough memory and ensure we're mutable
reserve(s + 1);
// append the character
if(is_alloc())
{
d.alloc.size++;
d.alloc.str[s] = c;
d.alloc.str[s + 1] = 0;
}
else
{
d.arr.set_size(s + 1);
d.arr.str[s] = c;
d.arr.str[s + 1] = 0;
}
}
void pop_back()
{
if(!empty())
resize(size() - 1);
}
void resize(const size_t s)
{
// if s is 0, fast path - if we're allocated just change the size, otherwise reset to an empty
// array representation.
if(s == 0)
{
if(is_alloc())
{
d.alloc.size = 0;
d.alloc.str[0] = 0;
return;
}
else
{
// either we're a fixed string, and we need to become an empty array, or we're already an
// array in which case we empty the array.
memset(&d, 0, sizeof(d));
return;
}
}
const size_t oldSize = size();
// call reserve first. This handles resizing up, and also making the string mutable if necessary
reserve(s);
// if the size didn't change, return.
if(s == oldSize)
return;
// now resize the string
if(is_alloc())
{
// if we resized upwards, memset the new elements to 0, if we resized down set the new NULL
// terminator
if(s > oldSize)
memset(d.alloc.str + oldSize, 0, s - oldSize + 1);
else
d.alloc.str[s] = 0;
// update the size.
d.alloc.size = s;
}
else
{
// if we resized upwards, memset the new elements to 0, if we resized down set the new NULL
// terminator
if(s > oldSize)
memset(d.arr.str + oldSize, 0, s - oldSize + 1);
else
d.arr.str[s] = 0;
// update the size.
d.arr.set_size(s);
}
}
size_t capacity() const
{
if(is_alloc())
return d.alloc.get_capacity();
if(is_fixed())
return d.fixed.size;
return d.arr.capacity;
}
size_t size() const
{
if(is_alloc() || is_fixed())
return d.fixed.size;
return d.arr.get_size();
}
size_t length() const { return size(); }
const char *c_str() const
{
if(is_alloc() || is_fixed())
return d.alloc.str;
return d.arr.str;
}
void clear() { resize(0); }
bool empty() const { return size() == 0; }
const char *data() const { return c_str(); }
char *data()
{
ensure_mutable();
return is_alloc() ? d.alloc.str : d.arr.str;
}
const char *begin() const { return c_str(); }
const char *end() const { return c_str() + size(); }
char front() const { return *c_str(); }
char &front()
{
ensure_mutable();
return data()[0];
}
char back() const { return *(end() - 1); }
char &back()
{
ensure_mutable();
return data()[size() - 1];
}
rdcstr substr(size_t offs, size_t length = ~0U)
{
const size_t sz = size();
if(offs >= sz)
return rdcstr();
if(length == ~0U || offs + length > sz)
length = sz - offs;
return rdcstr(c_str() + offs, length);
}
rdcstr &operator+=(const char *const str)
{
append(str, strlen(str));
return *this;
}
rdcstr &operator+=(const std::string &str)
{
append(str.c_str(), str.size());
return *this;
}
rdcstr &operator+=(const rdcstr &str)
{
append(str.c_str(), str.size());
return *this;
}
rdcstr operator+(const char *const str) const
{
rdcstr ret = *this;
ret += str;
return ret;
}
rdcstr operator+(const std::string &str) const
{
rdcstr ret = *this;
ret += str;
return ret;
}
rdcstr operator+(const rdcstr &str) const
{
rdcstr ret = *this;
ret += str;
return ret;
}
// Qt-type interface
bool isEmpty() const { return size() == 0; }
int32_t count() const { return (int32_t)size(); }
char takeAt(size_t offs)
{
char ret = c_str()[offs];
erase(offs);
return ret;
}
// Python interface
int32_t indexOf(char el, size_t first = 0, size_t last = ~0U) const
{
const char *str = c_str();
const size_t sz = size();
for(size_t i = first; i < sz && i < last; i++)
{
if(str[i] == el)
return (int32_t)i;
}
return -1;
}
int32_t find(const char *needle_str, size_t needle_len, size_t first = 0, size_t last = ~0U) const
{
const char *haystack = c_str();
const size_t haystack_len = size();
if(needle_len > haystack_len)
return -1;
if(needle_len == 0)
return 0;
for(size_t i = first; i <= haystack_len - needle_len && i < last; i++)
{
if(strncmp(haystack + i, needle_str, needle_len) == 0)
return (int32_t)i;
}
return -1;
}
int32_t find(const rdcstr &needle, size_t first = 0, size_t last = ~0U) const
{
return find(needle.c_str(), needle.size(), first, last);
}
int32_t find(const std::string &needle, size_t first = 0, size_t last = ~0U) const
{
return find(needle.c_str(), needle.size(), first, last);
}
int32_t find(const char *needle, size_t first = 0, size_t last = ~0U) const
{
return find(needle, strlen(needle), first, last);
}
bool contains(char needle) const { return indexOf(needle) != -1; }
bool contains(const rdcstr &needle) const { return find(needle) != -1; }
bool contains(const std::string &needle) const { return find(needle) != -1; }
bool contains(const char *needle) const { return find(needle) != -1; }
void removeOne(char el)
{
int idx = indexOf(el);
if(idx >= 0)
erase((size_t)idx);
}
// for equality check with rdcstr, check quickly for empty string comparisons
bool operator==(const rdcstr &o) const
{
if(o.size() == 0)
return size() == 0;
return !strcmp(o.c_str(), c_str());
}
// equality checks for other types, just check string directly
bool operator==(const char *const o) const
{
if(o == NULL)
return size() == 0;
return !strcmp(o, c_str());
}
bool operator==(const std::string &o) const { return o == c_str(); }
// for inverse check just reverse results of above
bool operator!=(const char *const o) const { return !(*this == o); }
bool operator!=(const std::string &o) const { return !(*this == o); }
bool operator!=(const rdcstr &o) const { return !(*this == o); }
// define ordering operators
bool operator<(const rdcstr &o) const { return strcmp(c_str(), o.c_str()) < 0; }
bool operator>(const rdcstr &o) const { return strcmp(c_str(), o.c_str()) > 0; }
// Qt compatibility
#if defined(RENDERDOC_QT_COMPAT)
rdcstr(const QString &in)
{
QByteArray arr = in.toUtf8();
memset(&d, 0, sizeof(d));
assign(arr.data(), (size_t)arr.size());
}
rdcstr(const QChar &in)
{
QByteArray arr = QString(in).toUtf8();
memset(&d, 0, sizeof(d));
assign(arr.data(), (size_t)arr.size());
}
operator QString() const { return QString::fromUtf8(c_str(), (int32_t)size()); }
operator QVariant() const { return QVariant(QString::fromUtf8(c_str(), (int32_t)size())); }
rdcstr &operator+=(const QString &str)
{
QByteArray arr = str.toUtf8();
append(arr.data(), (size_t)arr.size());
return *this;
}
rdcstr operator+(const QString &str) const
{
rdcstr ret = *this;
ret += str;
return ret;
}
rdcstr &operator+=(const QChar &chr)
{
QByteArray arr = QString(chr).toUtf8();
append(arr.data(), (size_t)arr.size());
return *this;
}
rdcstr operator+(const QChar &chr) const
{
rdcstr ret = *this;
ret += QString(chr);
return ret;
}
#endif
};
// macro that can append _lit to a macro parameter
#define STRING_LITERAL2(string) string##_lit
#define STRING_LITERAL(string) STRING_LITERAL2(string)
inline rdcstr operator+(const char *const left, const rdcstr &right)
{
return rdcstr(left) += right;
}
inline rdcstr operator+(const std::string &left, const rdcstr &right)
{
return rdcstr(left) += right;
}
inline bool operator==(const char *const left, const rdcstr &right)
{
return right == left;
}
inline bool operator==(const std::string &left, const rdcstr &right)
{
return right == left;
}
inline bool operator!=(const char *const left, const rdcstr &right)
{
return right != left;
}
inline bool operator!=(const std::string &left, const rdcstr &right)
{
return right != left;
}
inline std::ostream &operator<<(std::ostream &os, rdcstr const &str)
{
return os << str.c_str();
}
#if defined(RENDERDOC_QT_COMPAT)
inline rdcstr operator+(const QString &left, const rdcstr &right)
{
return rdcstr(left) += right;
}
inline rdcstr operator+(const QChar &left, const rdcstr &right)
{
return rdcstr(left) += right;
}
#endif
+34 -8
View File
@@ -1,15 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="rdcstr">
<DisplayString Condition="elems == 0">""</DisplayString>
<DisplayString>{elems,s}</DisplayString>
<StringView>elems,s</StringView>
<DisplayString Condition="d.fixed.flags &amp; rdcstr::FIXED_STATE">{d.fixed.str,s}</DisplayString>
<DisplayString Condition="d.fixed.flags &amp; rdcstr::ALLOC_STATE">{d.alloc.str,s}</DisplayString>
<DisplayString>{d.arr.str,s}</DisplayString>
<StringView Condition="d.fixed.flags &amp; rdcstr::FIXED_STATE">d.fixed.str,s</StringView>
<StringView Condition="d.fixed.flags &amp; rdcstr::ALLOC_STATE">d.alloc.str,s</StringView>
<StringView>d.arr.str,s</StringView>
<Expand>
<Item Name="[size]" ExcludeView="simple">usedCount</Item>
<Item Name="[capacity]" ExcludeView="simple">allocatedCount</Item>
<ArrayItems>
<Size>usedCount</Size>
<ValuePointer>elems</ValuePointer>
<Synthetic Name="[mode]" Condition="d.fixed.flags &amp; rdcstr::FIXED_STATE">
<DisplayString>Compile-Time Literal</DisplayString>
</Synthetic>
<Item Name="[size]" ExcludeView="simple" Condition="d.fixed.flags &amp; rdcstr::FIXED_STATE">d.fixed.size</Item>
<Item Name="[capacity]" ExcludeView="simple" Condition="d.fixed.flags &amp; rdcstr::FIXED_STATE">"N/A"</Item>
<ArrayItems Condition="d.fixed.flags &amp; rdcstr::FIXED_STATE">
<Size>d.fixed.size</Size>
<ValuePointer>d.fixed.str</ValuePointer>
</ArrayItems>
<Synthetic Name="[mode]" Condition="d.fixed.flags &amp; rdcstr::ALLOC_STATE">
<DisplayString>Heap-allocated String</DisplayString>
</Synthetic>
<Item Name="[size]" ExcludeView="simple" Condition="d.fixed.flags &amp; rdcstr::ALLOC_STATE">d.alloc.size</Item>
<Item Name="[capacity]" ExcludeView="simple" Condition="d.fixed.flags &amp; rdcstr::ALLOC_STATE">d.alloc._capacity</Item>
<ArrayItems Condition="d.fixed.flags &amp; rdcstr::ALLOC_STATE">
<Size>d.alloc.size</Size>
<ValuePointer>d.alloc.str</ValuePointer>
</ArrayItems>
<Synthetic Name="[mode]" Condition="(d.fixed.flags &amp; (rdcstr::ALLOC_STATE|rdcstr::FIXED_STATE)) == 0">
<DisplayString>Internal Array Small String</DisplayString>
</Synthetic>
<Item Name="[size]" ExcludeView="simple" Condition="(d.fixed.flags &amp; (rdcstr::ALLOC_STATE|rdcstr::FIXED_STATE)) == 0">d.arr._size</Item>
<Item Name="[capacity]" ExcludeView="simple" Condition="(d.fixed.flags &amp; (rdcstr::ALLOC_STATE|rdcstr::FIXED_STATE)) == 0">d.arr.capacity</Item>
<ArrayItems Condition="(d.fixed.flags &amp; (rdcstr::ALLOC_STATE|rdcstr::FIXED_STATE)) == 0">
<Size>d.arr._size</Size>
<ValuePointer>d.arr.str</ValuePointer>
</ArrayItems>
</Expand>
</Type>
+1
View File
@@ -166,6 +166,7 @@
<ClInclude Include="api\replay\data_types.h" />
<ClInclude Include="api\replay\gl_pipestate.h" />
<ClInclude Include="api\replay\pipestate.h" />
<ClInclude Include="api\replay\rdcstr.h" />
<ClInclude Include="api\replay\renderdoc_replay.h" />
<ClInclude Include="api\replay\replay_enums.h" />
<ClInclude Include="api\replay\shader_types.h" />
+3
View File
@@ -477,6 +477,9 @@
<ClInclude Include="data\glsl\glsl_globals.h">
<Filter>Resources\glsl</Filter>
</ClInclude>
<ClInclude Include="api\replay\rdcstr.h">
<Filter>API\Replay</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="maths\camera.cpp">
+456 -64
View File
@@ -24,6 +24,7 @@
#include "api/replay/renderdoc_replay.h"
#include "common/globalconfig.h"
#include "common/timing.h"
#include "os/os_specific.h"
#if ENABLED(ENABLE_UNIT_TESTS)
@@ -569,78 +570,469 @@ TEST_CASE("Test array type", "[basictypes]")
};
#define CHECK_NULL_TERM(str) CHECK(str.c_str()[str.size()] == '\0');
#define SMALL_STRING "Small str!"
#define LARGE_STRING \
"String literal that cannot be stored directly in a small-string optimisation array!"
#define VERY_LARGE_STRING \
R"(So: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce viverra dui dolor. Donec fermentum metus eu lorem rutrum, nec sodales urna vehicula. Praesent finibus tincidunt volutpat. Aliquam ullamcorper metus semper suscipit dignissim. Phasellus at odio nec arcu venenatis euismod id eget mi. Vestibulum consequat nisi sed massa venenatis, vel pellentesque nunc semper. Maecenas porttitor nulla non purus pellentesque pharetra. Ut ornare rhoncus massa at eleifend. Sed ultricies tincidunt bibendum. Pellentesque neque dolor, elementum eget scelerisque et, euismod at tortor. Duis vel porta sapien. Integer facilisis nisl condimentum tempor faucibus. Sed convallis tempus dolor quis fringilla. Nam dictum accumsan quam, eget pretium turpis mattis id. Praesent vitae enim ut est porttitor consectetur et at ante. Proin porttitor quam eu enim gravida, eget congue diam dapibus.!)"
TEST_CASE("Test string type", "[basictypes][string]")
{
rdcstr test;
RDCCOMPILE_ASSERT(sizeof(rdcstr) == sizeof(void *) * 3, "rdcstr is mis-sized");
// should not have any data in it
CHECK(test.size() == 0);
CHECK(test.capacity() == 0);
CHECK(test.empty());
CHECK(test.isEmpty());
CHECK(test.begin() == test.end());
CHECK(test.c_str() != NULL);
CHECK_NULL_TERM(test);
test = "Test string type";
CHECK(test.size() == 16);
CHECK(test.capacity() >= 16);
CHECK_FALSE(test.empty());
CHECK_FALSE(test.isEmpty());
CHECK(test.begin() + 16 == test.end());
CHECK(strlen(test.c_str()) == 16);
CHECK(test.c_str() != NULL);
CHECK(test == "Test string type");
CHECK(test == std::string("Test string type"));
CHECK(test == rdcstr("Test string type"));
CHECK_NULL_TERM(test);
test[4] = '!';
CHECK(test.size() == 16);
CHECK(test.capacity() >= 16);
CHECK_FALSE(test.empty());
CHECK_FALSE(test.isEmpty());
CHECK(test.begin() + 16 == test.end());
CHECK(strlen(test.c_str()) == 16);
CHECK(test.c_str() != NULL);
CHECK(test == "Test!string type");
CHECK(test == std::string("Test!string type"));
CHECK(test == rdcstr("Test!string type"));
CHECK_NULL_TERM(test);
test.clear();
CHECK(test.size() == 0);
CHECK(test.capacity() >= 16);
CHECK(test.empty());
CHECK(test.isEmpty());
CHECK(test.begin() == test.end());
CHECK_NULL_TERM(test);
rdcstr test2;
test2 = test;
CHECK(test.size() == test2.size());
CHECK(test.empty() == test2.empty());
for(size_t i = 0; i < test.size(); i++)
SECTION("Empty strings")
{
CHECK(test[i] == test2[i]);
}
const rdcstr test;
rdcstr empty;
// should not have any data in it
CHECK(test.size() == 0);
CHECK(test.empty());
CHECK(test.isEmpty());
CHECK(test.begin() == test.end());
test2 = empty;
CHECK(test.c_str() != NULL);
CHECK_NULL_TERM(test);
CHECK(test == "");
CHECK(test == ((const char *)NULL));
CHECK(test == rdcstr());
CHECK(test == std::string());
};
CHECK(test.size() == empty.size());
CHECK(test.empty() == empty.empty());
SECTION("Empty string after containing data")
{
rdcstr test;
auto lambda = [](rdcstr test, const char *str) {
test.clear();
CHECK(test.size() == 0);
CHECK(test.empty());
CHECK(test.isEmpty());
CHECK(test.begin() == test.end());
CHECK(test.c_str() != NULL);
CHECK_NULL_TERM(test);
};
lambda(SMALL_STRING, SMALL_STRING);
lambda(LARGE_STRING, LARGE_STRING);
lambda(VERY_LARGE_STRING, VERY_LARGE_STRING);
lambda(STRING_LITERAL(LARGE_STRING), LARGE_STRING);
};
SECTION("Small string read-only accessors")
{
auto lambda = [](const rdcstr &test, const char *str) {
const size_t len = strlen(str);
CHECK(test.size() == len);
CHECK(test.capacity() >= len);
CHECK_FALSE(test.empty());
CHECK_FALSE(test.isEmpty());
CHECK(test.begin() + len == test.end());
CHECK(strlen(test.c_str()) == len);
CHECK(test.c_str() != NULL);
CHECK(strcmp(test.c_str(), str) == 0);
CHECK(test != ((const char *)NULL));
CHECK(test == str);
CHECK(test == std::string(str));
CHECK(test == rdcstr(str));
CHECK_NULL_TERM(test);
CHECK(test.front() == 'S');
CHECK(test.back() == '!');
};
lambda(SMALL_STRING, SMALL_STRING);
lambda(LARGE_STRING, LARGE_STRING);
lambda(VERY_LARGE_STRING, VERY_LARGE_STRING);
lambda(STRING_LITERAL(LARGE_STRING), LARGE_STRING);
};
SECTION("String read-only accessors after modification")
{
auto lambda = [](rdcstr test, const char *str) {
const size_t len = strlen(str);
test[4] = '!';
CHECK(test.size() == len);
CHECK(test.capacity() >= len);
CHECK_FALSE(test.empty());
CHECK_FALSE(test.isEmpty());
CHECK(test.begin() + len == test.end());
CHECK(strlen(test.c_str()) == len);
CHECK(test.c_str() != NULL);
CHECK(strcmp(test.c_str(), str) == -1);
CHECK(test != str);
CHECK(test != std::string(str));
CHECK(test != rdcstr(str));
CHECK_NULL_TERM(test);
CHECK(test.front() == 'S');
CHECK(test.back() == '!');
};
lambda(SMALL_STRING, SMALL_STRING);
lambda(LARGE_STRING, LARGE_STRING);
lambda(VERY_LARGE_STRING, VERY_LARGE_STRING);
lambda(STRING_LITERAL(LARGE_STRING), LARGE_STRING);
};
SECTION("String copies")
{
auto lambda = [](const rdcstr &test, const char *str) {
rdcstr test2;
test2 = test;
CHECK(test.size() == test2.size());
CHECK(test.empty() == test2.empty());
for(size_t i = 0; i < test.size(); i++)
{
CHECK(test[i] == test2[i]);
}
CHECK(test.c_str() != test2.c_str());
rdcstr empty;
test2 = empty;
CHECK(test2.size() == empty.size());
CHECK(test2.empty() == empty.empty());
};
lambda(SMALL_STRING, SMALL_STRING);
lambda(LARGE_STRING, LARGE_STRING);
lambda(VERY_LARGE_STRING, VERY_LARGE_STRING);
lambda(STRING_LITERAL(LARGE_STRING), LARGE_STRING);
};
SECTION("Shrinking and expanding strings")
{
rdcstr test = "A longer string that would have been heap allocated";
test.resize(5);
CHECK(test.size() == 5);
CHECK_NULL_TERM(test);
CHECK(test == "A lon");
// should do nothing
test.resize(5);
CHECK(test.size() == 5);
CHECK_NULL_TERM(test);
CHECK(test == "A lon");
// this copy will copy to the internal array since it's small enough now
rdcstr test2 = test;
CHECK(test2.size() == 5);
CHECK_NULL_TERM(test2);
CHECK(test2 == "A lon");
test2 = "abcdefghij";
CHECK(test2.size() == 10);
CHECK_NULL_TERM(test2);
test2.resize(3);
CHECK(test2.size() == 3);
CHECK_NULL_TERM(test2);
test2.resize(6);
CHECK(test2.size() == 6);
CHECK_NULL_TERM(test2);
test.resize(12345);
CHECK(test.capacity() == 12345);
CHECK(test.size() == 12345);
const char *prev_ptr = test.c_str();
// this could fit in the internal array but to avoid allocation thrashing we should keep the
// same allocation
test = "Short str";
CHECK(test.capacity() == 12345);
CHECK(test.size() == 9);
CHECK(test.c_str() == prev_ptr);
CHECK_NULL_TERM(test);
CHECK(test == "Short str");
test.resize(4);
CHECK(test.size() == 4);
CHECK_NULL_TERM(test);
CHECK(test == "Shor");
test.resize(8);
CHECK(test.size() == 8);
CHECK_NULL_TERM(test);
CHECK(test == "Shor");
CHECK(test[4] == 0);
CHECK(test[5] == 0);
CHECK(test[6] == 0);
CHECK(test[7] == 0);
};
SECTION("erase")
{
rdcstr test = "Hello, World! This is a test string";
test.erase(0);
CHECK(test == "ello, World! This is a test string");
test.erase(0, 4);
CHECK(test == ", World! This is a test string");
test.erase(9, 5);
CHECK(test == ", World! is a test string");
test.erase(14, 1000);
CHECK(test == ", World! is a ");
test.erase(100);
CHECK(test == ", World! is a ");
test.erase(100, 100);
CHECK(test == ", World! is a ");
};
SECTION("append")
{
rdcstr test = "Hello";
test += " World";
CHECK(test.size() == 11);
CHECK_NULL_TERM(test);
CHECK(test == "Hello World");
rdcstr test2 = test + "!";
CHECK(test2.size() == 12);
CHECK_NULL_TERM(test2);
CHECK(test2 == "Hello World!");
test2 += " And enough characters to force an allocation";
CHECK(test2 == "Hello World! And enough characters to force an allocation");
test2 += ", " + test + "?";
CHECK(test2 == "Hello World! And enough characters to force an allocation, Hello World?");
};
SECTION("insert")
{
rdcstr test = "Hello World!";
test.insert(5, ",");
CHECK(test == "Hello, World!");
rdcstr test2 = test;
test2.insert(0, test);
CHECK(test2 == "Hello, World!Hello, World!");
test2.insert(100, "foo");
CHECK(test2 == "Hello, World!Hello, World!");
};
SECTION("push_back and pop_back")
{
rdcstr test = "Hello, World!";
test.push_back('!');
CHECK(test == "Hello, World!!");
test.push_back('!');
CHECK(test == "Hello, World!!!");
test.pop_back();
CHECK(test == "Hello, World!!");
test.pop_back();
CHECK(test == "Hello, World!");
test.pop_back();
CHECK(test == "Hello, World");
test.clear();
CHECK(test == "");
test.pop_back();
CHECK(test == "");
test = "Longer string to force a heap allocation: Hello, World!";
test.push_back('!');
CHECK(test == "Longer string to force a heap allocation: Hello, World!!");
test.pop_back();
CHECK(test == "Longer string to force a heap allocation: Hello, World!");
test.pop_back();
CHECK(test == "Longer string to force a heap allocation: Hello, World");
test.clear();
CHECK(test == "");
test.pop_back();
CHECK(test == "");
};
SECTION("substr")
{
rdcstr test = "Hello, World!";
CHECK(test.substr(0) == "Hello, World!");
CHECK(test.substr(1) == "ello, World!");
CHECK(test.substr(5) == ", World!");
CHECK(test.substr(13) == "");
CHECK(test.substr(100) == "");
CHECK(test.substr(5, 2) == ", ");
CHECK(test.substr(5, 100) == ", World!");
test = "Hello, World! Hello, World! Hello, World! Hello, World! Hello, World!";
CHECK(test.substr(0) ==
"Hello, World! Hello, World! Hello, World! Hello, World! Hello, World!");
CHECK(test.substr(1) == "ello, World! Hello, World! Hello, World! Hello, World! Hello, World!");
CHECK(test.substr(5) == ", World! Hello, World! Hello, World! Hello, World! Hello, World!");
CHECK(test.substr(13) == " Hello, World! Hello, World! Hello, World! Hello, World!");
CHECK(test.substr(69) == "");
CHECK(test.substr(100) == "");
CHECK(test.substr(5, 2) == ", ");
CHECK(test.substr(5, 100) ==
", World! Hello, World! Hello, World! Hello, World! Hello, World!");
test = "Hello, World! Hello, World! Hello, World! Hello, World! Hello, World!"_lit;
CHECK(test.substr(0) ==
"Hello, World! Hello, World! Hello, World! Hello, World! Hello, World!");
CHECK(test.substr(1) == "ello, World! Hello, World! Hello, World! Hello, World! Hello, World!");
CHECK(test.substr(5) == ", World! Hello, World! Hello, World! Hello, World! Hello, World!");
CHECK(test.substr(13) == " Hello, World! Hello, World! Hello, World! Hello, World!");
CHECK(test.substr(69) == "");
CHECK(test.substr(100) == "");
CHECK(test.substr(5, 2) == ", ");
CHECK(test.substr(5, 100) ==
", World! Hello, World! Hello, World! Hello, World! Hello, World!");
};
SECTION("searching")
{
rdcstr test = "Hello, World!";
CHECK(test.find("Hello") == 0);
CHECK(test.find("World") == 7);
CHECK(test.find("ld!") == 10);
CHECK(test.find("Foobar") == -1);
CHECK(test.find("Hello, World!!") == -1);
CHECK(test.find("Hello, World?") == -1);
CHECK(test.find("") == 0);
CHECK(test.indexOf('H') == 0);
CHECK(test.indexOf('l') == 2);
CHECK(test.indexOf('?') == -1);
CHECK(test.contains('!'));
CHECK_FALSE(test.contains('?'));
CHECK(test.contains('H'));
CHECK(test.contains("Hello"));
char H = test.takeAt(0);
CHECK(H == 'H');
CHECK_FALSE(test.contains('H'));
CHECK_FALSE(test.contains("Hello"));
test.removeOne('!');
CHECK_FALSE(test.contains('!'));
CHECK(test == "ello, World");
};
SECTION("String literal tests")
{
rdcstr test = STRING_LITERAL(LARGE_STRING);
const size_t len = strlen(LARGE_STRING);
CHECK(test.size() == len);
CHECK(test.capacity() == test.size());
CHECK(strlen(test.c_str()) == test.size());
rdcstr test2;
test2.resize(12345);
test2 = test;
CHECK(test2.size() == len);
CHECK(test2.capacity() == test2.size());
CHECK(test == test2);
// should both be pointing directly to the string storage, so identical pointers
CHECK(test.c_str() == test2.c_str());
test2.reserve(1);
// they will be equal still but not with the same storage now
CHECK(test == test2);
CHECK(test.c_str() != test2.c_str());
test2[0] = '!';
CHECK(test != test2);
test = test2;
// equal now but still not with the same storage
CHECK(test == test2);
CHECK(test.c_str() != test2.c_str());
test = "short literal"_lit;
test2 = test;
// this does the copy-on-write but into the internal array
test[0] = 'S';
CHECK(test == "Short literal");
CHECK(test.size() == test2.size());
};
};
#endif // ENABLED(ENABLE_UNIT_TESTS)