Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Twerk from Home
Jan 17, 2009

This avatar brought to you by the 'save our dead gay forums' foundation.
Is there any widely-used and ergonomic facility for zero-copy views into const strings? I feel like https://en.cppreference.com/w/cpp/string/basic_string_view is what I'm reaching for, but it looks like I'd need to touch raw pointers by hand to split a string. I have an application that's spending most of its time reading in input lines from a textual tabular format, but the actual useful work is pretty fast. It's not blocked on disk I/O, and right now it's using getline to read whole lines in, passing a pointer to that line through a queue to a pool of worker threads, each of which is wrapping a complete line in an istringstream and reading from it with >> in a loop, which is slow as balls.

The profiler shows that 90% of total CPU time is spent in basic_istream::sentry::sentry, meaning this approach is really expensive. As a first shot I'm just going to do boost::algorithm::split and split into a vector of strings, but there's got to be a better way to do this. A regex iterator would also copy, and I'm just reading from a const stream, there must be a zero-copy way to do this that doesn't involve falling back to manually walking it and saving a collection of pointers. Even if I give up and roll this C-style, I couldn't use strtok if I wanted to because there's between 8 and 64 threads doing this work at any given time, and strtok isn't thread safe.

Edit: This is basically what I'm wanting to do: https://techoverflow.net/2017/01/23/zero-copy-in-place-string-splitting-in-c/.

Edit again: ohhhh this looks right up my alley: https://www.boost.org/doc/libs/1_70_0/libs/tokenizer/doc/tokenizer.htm

I swear, I am unable to find solutions until after I've made a dumb post about it.

Twerk from Home fucked around with this message at 04:30 on Jul 17, 2022

Adbot
ADBOT LOVES YOU

roomforthetuna
Mar 22, 2005

I don't need to know anything about virii! My CUSTOM PROGRAM keeps me protected! It's not like they'll try to come in through the Internet or something!

Twerk from Home posted:

Is there any widely-used and ergonomic facility for zero-copy views into const strings?
absl::string_view comes with a whole lot of utility functions too, including StrSplit into a vector or string_views or even into a std::pair of string_views (or strings).

Sweeper
Nov 29, 2007
The Joe Buck of Posting
Dinosaur Gum

Twerk from Home posted:

Is there any widely-used and ergonomic facility for zero-copy views into const strings? I feel like https://en.cppreference.com/w/cpp/string/basic_string_view is what I'm reaching for, but it looks like I'd need to touch raw pointers by hand to split a string. I have an application that's spending most of its time reading in input lines from a textual tabular format, but the actual useful work is pretty fast. It's not blocked on disk I/O, and right now it's using getline to read whole lines in, passing a pointer to that line through a queue to a pool of worker threads, each of which is wrapping a complete line in an istringstream and reading from it with >> in a loop, which is slow as balls.

The profiler shows that 90% of total CPU time is spent in basic_istream::sentry::sentry, meaning this approach is really expensive. As a first shot I'm just going to do boost::algorithm::split and split into a vector of strings, but there's got to be a better way to do this. A regex iterator would also copy, and I'm just reading from a const stream, there must be a zero-copy way to do this that doesn't involve falling back to manually walking it and saving a collection of pointers. Even if I give up and roll this C-style, I couldn't use strtok if I wanted to because there's between 8 and 64 threads doing this work at any given time, and strtok isn't thread safe.

Edit: This is basically what I'm wanting to do: https://techoverflow.net/2017/01/23/zero-copy-in-place-string-splitting-in-c/.

Edit again: ohhhh this looks right up my alley: https://www.boost.org/doc/libs/1_70_0/libs/tokenizer/doc/tokenizer.htm

I swear, I am unable to find solutions until after I've made a dumb post about it.

strtok_r is thread safe if you really want to use it. I have not found anything in the standard for dealing with strings reasonably. Almost all of my string data is super simple so I don't have to worry about crazy unicode stuff so I'd probably try to do this:

Instead of splitting into small entries, read big chunks of the file and pass the entire thing (build w/ a stringstream, make a string, should std::move? not super efficient, but easy) to a processor to handle it in full instead of splitting it into smaller pieces. Makes it easier to manage the data, once you use a string view you have to track ownership of the original piece of data, so by moving big pieces you have to manage less ownership. if you are iterating the string to read chunks (at least N bytes, end on newline or something) you could also note the delimiters as you go and pass those alongside the data.

How fast does it need to be? I might mmap the file so I never have to copy things and I can simply make string_views into the mmap'd file if possible. Create a thread that does nothing but split the mmap'd region into views of views (columns/lines?) and pass on the chunks to workers.

Xarn
Jun 26, 2015

Twerk from Home posted:

Is there any widely-used and ergonomic facility for zero-copy views into const strings? I feel like https://en.cppreference.com/w/cpp/string/basic_string_view is what I'm reaching for, but it looks like I'd need to touch raw pointers by hand to split a string. I have an application that's spending most of its time reading in input lines from a textual tabular format, but the actual useful work is pretty fast. It's not blocked on disk I/O, and right now it's using getline to read whole lines in, passing a pointer to that line through a queue to a pool of worker threads, each of which is wrapping a complete line in an istringstream and reading from it with >> in a loop, which is slow as balls.

The profiler shows that 90% of total CPU time is spent in basic_istream::sentry::sentry, meaning this approach is really expensive. As a first shot I'm just going to do boost::algorithm::split and split into a vector of strings, but there's got to be a better way to do this. A regex iterator would also copy, and I'm just reading from a const stream, there must be a zero-copy way to do this that doesn't involve falling back to manually walking it and saving a collection of pointers. Even if I give up and roll this C-style, I couldn't use strtok if I wanted to because there's between 8 and 64 threads doing this work at any given time, and strtok isn't thread safe.

Edit: This is basically what I'm wanting to do: https://techoverflow.net/2017/01/23/zero-copy-in-place-string-splitting-in-c/.

Edit again: ohhhh this looks right up my alley: https://www.boost.org/doc/libs/1_70_0/libs/tokenizer/doc/tokenizer.htm

I swear, I am unable to find solutions until after I've made a dumb post about it.

Just two notes on this:

small strings don't allocate memory, so depending on your data you might be fine either way.
If you need to send the substrings into API that needs zero termination, you are doomed to a copy anyway.

Xarn
Jun 26, 2015

Foxfire_ posted:

Headers specific to separately compiled libraries whose binaries end up in /usr/local/lib (the ones that are under libs/LibraryName/src/, not boost/include/LibraryName/ in the boost source code) use normal quotes since if there's a local vs system conflict while building that library, they want the local file. Like if you put a file named cpuid.hpp in /usr/local/include/, compiling Boost's Atomic library still wants its own file, not that one

I am gonna start here. If Boost.Atomic wants its own cpuid.hpp file, then it should place it somewhere like boost/atomic/detail/cpuid.hpp, and include it as #include %boost/atomic/detail/cpuid.hpp%, not cross its fingers, hope for the best, and use file-location-relative include path, because

code:
#include "cpuid.hpp" // we want our own cpuid.hpp, not system one
is one small refactoring away from silently including the one in your global include dir anyway. (If you move cpuid, or the including file, #include "cpuid.hpp" will fail, get rewritten to #include <cpuid.hpp> and find the one that you explicitly do not want.)


Using the full in-project path, so you do #include "boost/atomic/detail/cpuid.hpp" at least removes the "welp, I moved the including file and now I picked up completely different header" failure mode. Due to the rewriting behaviour, you can still run into issues if you installed an old version of your library, and then start moving around headers in your checkout, but there isn't really a way to guard against that except keeping your devenv clean.


Foxfire_ posted:

Using from-the-base-of-your-project paths seems like a reasonable goal to me, but it'd be better implemented by sticking to the normal <> vs "" convention and modifying the user include path to include the root of the project instead of modifying the system include path (-iquote on gcc vs -I). One extra stat doesn't seem likely to actually matter to compile time enough to be worth doing something unusual.

Tricks with iquote don't work on MSVC or nvcc (at least I haven't seen iquote equivalent in nvcc's docs) and this is what the docs say for MSVC "" includes

quote:

If the #include directive is specified using double-quote form, it first searches local directories. The search begins in the same directory as the file that contains the #include directive. If it fails to find the file, it searches next in the directories of the currently opened include files, in the reverse order in which they were opened.

so if you use "" you end up searching loving everywhere :v:

Twerk from Home
Jan 17, 2009

This avatar brought to you by the 'save our dead gay forums' foundation.

Xarn posted:

Just two notes on this:

small strings don't allocate memory, so depending on your data you might be fine either way.
If you need to send the substrings into API that needs zero termination, you are doomed to a copy anyway.

Thanks to everyone who threw in suggestions, it was extremely helpful. I'm still doing copying as I am parsing the lines into my own data structure, but swapping out istringstream with operator>> for boost::tokenizer resulted in runtime by wall clock being 1/10th what it was before, with CPU time being 1/20th. 20x performance increase! The worker threadpool now can be much smaller, or even just one thread, which is going to let me now accept other types of files more easily with a single thread filling a different, more complicated data structure that would be harder to share between threads.

I'm sure that the mmap'd file could make this thing really rip and get me another 4x performance, but if I mmaped the file then it'd be more complicated to read in compressed input, and I'd be having to copy out uncompressed lines somewhere anyway. Here's an article I stumbled on mentioning how optimized Perl can be for some plain file reading / string parsing situations and comparing it to mmap-ing: https://yongweiwu.wordpress.com/2017/09/14/c-cxx-performance-mmap-and-string_view/

Next issue on the plate for me is that this application and several others my group works with are and have been using unsynchronized bools as flags for communications between threads. I'm pretty sure that these need to be atomic<bool> , or protected with mutexes or something but it's been working like this for years. I assume we've just gotten lucky with undefined behavior so far? Also, I'm glad I had a reference book around, because my Java-brained rear end was about to slap "volatile" on there, but it turns out that volatile does not create memory fences in C++.

Twerk from Home fucked around with this message at 19:21 on Jul 18, 2022

Xarn
Jun 26, 2015

Twerk from Home posted:


Next issue on the plate for me is that this application and several others my group works with are and have been using unsynchronized bools as flags for communications between threads. I'm pretty sure that these need to be atomic<bool> , or protected with mutexes or something but it's been working like this for years. I assume we've just gotten lucky with undefined behavior so far? Also, I'm glad I had a reference book around, because my Java-brained rear end was about to slap "volatile" on there, but it turns out that volatile does not create memory fences in C++.

It does on MSVC, but uh, go use atomics/mutexes so it doesn't explode in your face later.


Also resist the siren call of non seqcst atomics. The perf is not worth it :v:

Beef
Jul 26, 2004
The non-atomic bools might be working because they are write-once? It's not a bad thing if it is. That is, you are not implementing critical sections with it, because the lack of pipeline flushes and compiler barrier will gently caress that up.

Beef fucked around with this message at 20:56 on Jul 18, 2022

Presto
Nov 22, 2002

Keep calm and Harry on.
I use quotes for headers that were written by us, and angle brackets for anything out of my control.

You know, the way God intended.

leper khan
Dec 28, 2010
Honest to god thinks Half Life 2 is a bad game. But at least he likes Monster Hunter.

Presto posted:

I use quotes for headers that were written by us, and angle brackets for anything out of my control.

You know, the way God intended.

qsvui
Aug 23, 2003
some crazy thing
If God was responsible for C++, I'd be ignoring his intentions for good reason.

Absurd Alhazred
Mar 27, 2010

by Athanatos
C++ is definitely absent of God.

imperiusdamian
Dec 8, 2021

Absurd Alhazred posted:

C++ is definitely absent of God.

God codes in Lisp.

Xarn
Jun 26, 2015

qsvui posted:

If God was responsible for C++, I'd be ignoring his intentions for good reason.

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe

Xarn posted:

Also resist the siren call of non seqcst atomics. The perf is not worth it :v:

:(

I agree that trying to use relaxed is rarely a good idea, but acquire/release are fine in basically every situation where atomics work at all.

The way I always think about it is that there are a lot of circumstances where lock-free atomics aren’t good enough to get the semantics you want, and a tiny fraction of them happen to be fixable by sequential consistency.

rjmccall fucked around with this message at 17:27 on Jul 19, 2022

repiv
Aug 13, 2009

Don't non-SeqCst atomics make a bigger difference on ARM than they do on x86?

My rough understanding is that all atomics compile down to the equivalent of SeqCst on x86, so looser atomics only affect how the compiler is allowed to optimize, but on ARM there's separate instructions with weaker guarantees that are potentially faster for weaker atomics

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
You actually do need slightly stronger operations to do seq_cst on x86 because the architecture allows loads to be reordered with stores. The usual approach is to do seq_cst stores with XCHG because that’s semantically also a load and therefore cannot be reordered with other loads.

On ARM, it’s complicated. ARM64 atomics are naturally seq_cst. ARM32 needs explicit fences to do seq_cst, IIRC.

Twerk from Home
Jan 17, 2009

This avatar brought to you by the 'save our dead gay forums' foundation.

Beef posted:

The non-atomic bools might be working because they are write-once? It's not a bad thing if it is. That is, you are not implementing critical sections with it, because the lack of pipeline flushes and compiler barrier will gently caress that up.

They're being used to tell threads that work is done, in a really hacky way. Multiple threads are reading from a queue, and then when all work has been submitted to the queue then the queue empties, the publisher thread sets a bool* to true that all of the threads are seeing and using as a signal to stop polling the queue. The threads are polling in a loop, and then sleeping if the queue was empty and it didn't get work.

I'm guessing a better approach would be actually using condition_variable on the lock with wait_for, but then I'm still going to need to have it periodically checking the flag to see if it should be shut down so in the real world it's not much different, other than a thread getting notified when something new came through the queue, so it wakes up and starts sooner than the current sleep-based waiting.

Edit: Actually, I guess I could get rid of timers entirely by having all the threads wait() on the lock protecting the queue, and then notify_one when I put something into the queue, then when it's time to shut everything down set the flag then notify_all.

Twerk from Home fucked around with this message at 21:54 on Jul 19, 2022

nielsm
Jun 1, 2009



If you need to notify of/wait for job completion, a std::promise might be the most appropriate way of expressing that.

giogadi
Oct 27, 2009

Not to mention lldb is a huge buggy piece of poo poo. I use an old macbook for game dev and I’m thinking of switching to gcc just so I can maybe have better luck with gdb

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed
You can use gdb with clang and lldb with gcc.

giogadi
Oct 27, 2009

Plorkyeran posted:

You can use gdb with clang and lldb with gcc.

Oh really? For some reason I’ve had a lot of trouble getting gdb to work on my clang builds on mac

Xarn
Jun 26, 2015

rjmccall posted:

:(

I agree that trying to use relaxed is rarely a good idea, but acquire/release are fine in basically every situation where atomics work at all.

The way I always think about it is that there are a lot of circumstances where lock-free atomics aren’t good enough to get the semantics you want, and a tiny fraction of them happen to be fixable by sequential consistency.

I agree, but we are talking about a person coming from Java, who just now figured out that volatile doesn't mean atomic. Chances of them happily applying acquire-release properly is low, and would get them bogged down in something likely doesn't matter for them.

Twerk from Home
Jan 17, 2009

This avatar brought to you by the 'save our dead gay forums' foundation.

nielsm posted:

If you need to notify of/wait for job completion, a std::promise might be the most appropriate way of expressing that.

Yeah, to translate from Java, I'm really missing having a ThreadPoolExecutor that manages its own queue of incoming tasks, can be .shutdown() into a state that allows it to complete all of the tasks currently in the queue, and returns promises. It looks like Folly has some pretty nice ThreadPoolExecutors available: https://github.com/facebook/folly/blob/main/folly/docs/Executors.md and I also see that Boost has a thread_pool: https://www.boost.org/doc/libs/1_79_0/doc/html/boost_asio/reference/thread_pool.html. That's certainly the way we'll go in the future, where appropriate. At a glance, boost_asio is header-only, which means it'd be a pretty easy dependency to introduce.

What this application has right now is a std::queue and worker threads polling it in a loop based on a timer, with a shared boolean controlling the loop, as I mentioned. I do want a way to make the queue have a fixed maximum size, to provide backpressure so that if worker threads are not keeping up with the input thread, it will slow adding to the queue. If I do a relatively straightforward bounded blocking queue type solution like this one, then I'd need some way to interrupt threads that are blocked waiting for an item to be added to the queue when it's time to shut down.

It looks like Boost has a solution for that as well, but Boost.Thread is new to me and we're just using std::thread right now. I'm also not 100% confident that I understand the docs fully. It looks to me like boost::thread::interrupt will interrupt threads that are wait-ing with a boost::thread_interrupted exception, but not threads that are currently doing work, which is pretty cool. That makes it sound like when all work had been submitted to the queue and the queue was empty, then I could call .interrupt() on all of the threads and the next time they hit the wait() in the loop, an exception would be thrown.

Foxfire_
Nov 8, 2010

I don't like the boost thing because:
(A) throwing exceptions for normal control flow are almost never a good idea for understandability
and
(B) it will be throwing from any of boost interruption point that happens to be in the middle of the work being done, not just if a thread was waiting on the queue (unless you jump through more hoops to suppress that for all the other worker code)

It should be straightforward to modify your simple synchronized queue with:
- A shutdown() operation that sets a flag (inside the mutex) and then notify_all()'s the condition variable to wake up anyone who was sleeping on it
- Change the dequing operations to:
A) On entry, test if the shutdown flag is set and return a "Queue is shutdown" indicator instead of sleeping
B) Once they're asleep, change the condition being waited on so that instead of waiting for size > 0, they wait for (size > 0) || shutdown and then returns the popped item / "Queue is shutdown" accordingly

Xarn
Jun 26, 2015
My last post about includes was controversial, but can we at least agree that using "" for external and system headers should be criminal?

csammis
Aug 26, 2003

Mental Institution

Xarn posted:

My last post about includes was controversial, but can we at least agree that using "" for external and system headers should be criminal?

:yeah:

BattleMaster
Aug 14, 2000

What is the scope of declarations within the initializer of a for loop? Does it vary across different implementations or standards?

I've previously run into compilers freaking out about multiple declarations from the following:

code:
for (int i = 0; ... ) { ... }
for (int i = 0; ... ) { ... }
So I've been doing

code:
int i;
for (i = 0; ... ) { ... }
for (i = 0; ... ) { ... }
But I'm not sure if issues with the former were sketchy compilers or what. But I can't find an answer via a search because I guess there are too many generic words in "c for initializer scope" :confused:

edit: Of course I could just try and see what happens with a given compiler but I'm mainly wondering about the recommended/most portable way is

BattleMaster fucked around with this message at 20:11 on Jul 27, 2022

leper khan
Dec 28, 2010
Honest to god thinks Half Life 2 is a bad game. But at least he likes Monster Hunter.

BattleMaster posted:

What is the scope of declarations within the initializer of a for loop? Does it vary across different implementations or standards?

I've previously run into compilers freaking out about multiple declarations from the following:

code:
for (int i = 0; ... ) { ... }
for (int i = 0; ... ) { ... }
So I've been doing

code:
int i;
for (i = 0; ... ) { ... }
for (i = 0; ... ) { ... }
But I'm not sure if issues with the former were sketchy compilers or what. But I can't find an answer via a search because I guess there are too many generic words in "c for initializer scope" :confused:

Have to do this in the language version I'm using.

code:

void myfunc(void) {
  ...
  {
    int i;
    for (i=0; ...) {...}
  }
}

Absurd Alhazred
Mar 27, 2010

by Athanatos

BattleMaster posted:

What is the scope of declarations within the initializer of a for loop? Does it vary across different implementations or standards?

I've previously run into compilers freaking out about multiple declarations from the following:

code:
for (int i = 0; ... ) { ... }
for (int i = 0; ... ) { ... }
So I've been doing

code:
int i;
for (i = 0; ... ) { ... }
for (i = 0; ... ) { ... }
But I'm not sure if issues with the former were sketchy compilers or what. But I can't find an answer via a search because I guess there are too many generic words in "c for initializer scope" :confused:

edit: Of course I could just try and see what happens with a given compiler but I'm mainly wondering about the recommended/most portable way is

According to the Cpp Reference for for:

quote:

C++ code:
for ( init-statement ; condition(optional) ; iteration-expression(optional) ) statement
...
The above syntax produces code equivalent to:
C++ code:
{
    init-statement
    while ( condition ) {
        statement
        iteration-expression ;
    }
}

There are probably versions of C++, C, Javascript, etc, that do this differently, but here at least it seems like you shouldn't have had issues just having two for loops with the same initialized variable internally.

more falafel please
Feb 26, 2005

forums poster

Not quoting standard here, but I was under the impression C's for initialization is in the enclosing scope and C++'s is in its own.

Absurd Alhazred
Mar 27, 2010

by Athanatos
Hmmm... putting
C++ code:
#include <stdio.h>

int main(int argc, char** argv)
{
    int test = 0;
    for (int i = 0; i < 10; ++i)
    {
        test += i;
    }
    for (int i = 0; i < 10; ++i)
    {
        test += i;
    }

    printf("Test: %d", test);
}
Into Godbolt as C and choosing x86-64 gcc 4.1.2 leads to a redefinition error, while 12.1 (latest numbered) is fine. C++ is fine in both.

Foxfire_
Nov 8, 2010

C89 didn't allow declaring a new variable in a for loop control part. It was a common compiler extension to allow that, but whether it is scoped to the inside of the loop or not was inconsistent between different compilers language extensions. GCC will refuse it entirely if you pass --std=c89

ArcticZombie
Sep 15, 2010
Probably not this because you’re getting a redefinition error, but in C89 you can’t declare the variable inside the for (), you must use a previously declared variable.

e: took too long.

BattleMaster
Aug 14, 2000

Thanks, everyone. So the way I'm already doing it seems like the way to do it then.

cheetah7071
Oct 20, 2010

honk honk
College Slice
Does anyone in here have advice on packages for making simple GUIs? I know the usual advice is to write your GUI in another language even if your backend is C++, but what I want is so simple that it feels like it really ought to be easier to not add another language to the project. All I really need is things like radio select buttons, checkboxes, text input, file select prompts--basically just a visual wrapper for the same options present on the command line interface. It needs to be open source (or at least usable in an open source project). Cross-platform would be nice but I'll take what I can get--windows is the minimum.

Every package I've found seems to be insanely heavy-weight, intended for people writing extremely complex software way beyond the capacity I actually need, with complexity and difficulty to link/include commensurate with their power

Foxfire_
Nov 8, 2010

BattleMaster posted:

Thanks, everyone. So the way I'm already doing it seems like the way to do it then.
If you have a choice, don't use C89. Look in your compiler documentation to see if it has a way to opt in to C99/C11/C17/draft.

Absurd Alhazred
Mar 27, 2010

by Athanatos

Foxfire_ posted:

C89 didn't allow declaring a new variable in a for loop control part. It was a common compiler extension to allow that, but whether it is scoped to the inside of the loop or not was inconsistent between different compilers language extensions. GCC will refuse it entirely if you pass --std=c89

:doh:
Uh... well, anyway, play around with Godbolt until you get the right complaint, I guess. :sweatdrop:

cheetah7071 posted:

Does anyone in here have advice on packages for making simple GUIs? I know the usual advice is to write your GUI in another language even if your backend is C++, but what I want is so simple that it feels like it really ought to be easier to not add another language to the project. All I really need is things like radio select buttons, checkboxes, text input, file select prompts--basically just a visual wrapper for the same options present on the command line interface. It needs to be open source (or at least usable in an open source project). Cross-platform would be nice but I'll take what I can get--windows is the minimum.

Every package I've found seems to be insanely heavy-weight, intended for people writing extremely complex software way beyond the capacity I actually need, with complexity and difficulty to link/include commensurate with their power

I really liked working with ImGui. It's immediate mode, so you have to call it every frame. The benefit is that you don't have to do a lot of setting up to do things, you just do them as you need them.

giogadi
Oct 27, 2009

Seconding imgui. I no longer dread doing UI stuff for tools.

Adbot
ADBOT LOVES YOU

cheetah7071
Oct 20, 2010

honk honk
College Slice
Showing my own ignorance here, but is it really necessary to be backed by a proper rendering engine like directx or opengl if all I want is buttons and file select prompts and stuff? I just sort of assumed that that wouldn't be necessary, but maybe that's wrong because every library I've looked at has been a generalizing abstraction over rendering engines.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply