2016-08-02 15 views
19

Tôi đang cố giới hạn khung hình trên giây trong vòng lặp đang thực hiện kiểm tra giao lộ, sử dụng C++ với chrono và chuỗi.Làm thế nào để giới hạn FPS trong một vòng lặp với C + +?

Đây là mã của tôi:

std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); 
std::chrono::system_clock::time_point lastFrame = std::chrono::system_clock::now(); 

while (true) 
{ 
    // Maintain designated frequency of 5 Hz (200 ms per frame) 
    now = std::chrono::system_clock::now(); 
    std::chrono::duration<double, std::milli> delta = now - lastFrame; 
    lastFrame = now; 

    if (delta.count() < 200.0) 
    { 
     std::chrono::duration<double, std::milli> delta_ms(200.0 - delta.count()); 
     auto delta_ms_duration = std::chrono::duration_cast<std::chrono::milliseconds>(delta_ms); 
     std::this_thread::sleep_for(std::chrono::milliseconds(delta_ms_duration.count())); 
    } 

    printf("Time: %f \n", delta.count()); 

    // Perform intersection test 

} 

Vấn đề tôi đang gặp là mỗi đầu ra khác của đồng bằng đang cho thấy một lượng rất nhỏ, chứ không phải là ~ 200 ms/khung Tôi đang nhắm đến:

Time: 199.253200 
Time: 2.067700 
Time: 199.420400 
Time: 2.408100 
Time: 199.494200 
Time: 2.306200 
Time: 199.586800 
Time: 2.253400 
Time: 199.864000 
Time: 2.156500 
Time: 199.293800 
Time: 2.075500 
Time: 201.787500 
Time: 4.426600 
Time: 197.304100 
Time: 4.530500 
Time: 198.457200 
Time: 3.482000 
Time: 198.365300 
Time: 3.415400 
Time: 198.467400 
Time: 3.595000 
Time: 199.730100 
Time: 3.373400 

Bất kỳ suy nghĩ nào về lý do tại sao điều này xảy ra?

+3

Thời gian của bạn có vẻ là khoảng 200ms, vậy vấn đề là gì? Bạn không thể mong đợi để có được bất cứ điều gì gần thời gian chính xác .. bạn nhận được một ít hơn 200, sau đó bạn nhận được một chút nhỏ hơn, đối phó với nó - bạn sẽ không bao giờ nhận được chính xác 200 mỗi lần. –

+2

Tôi không nhắm đến thời gian chính xác, nhưng tôi không hiểu tại sao một số vòng lặp lặp lại là 1-4 ms và các vòng lặp khác là ~ 200 ms. – Irongrave

+2

vì đôi khi deltacount của bạn <200 và bạn vẫn xuất nó – strangeqargo

Trả lời

14

Nếu bạn nghĩ về cách mã của bạn hoạt động, bạn sẽ thấy rằng nó hoạt động chính xác cách bạn viết nó. Delta dao động vì một sai lầm logic trong mã.

Đây là những gì sẽ xảy ra:

  • Chúng tôi bắt đầu với delta == 0.
  • Do delta nhỏ hơn 200, bạn mã ngủ 200 - delta(0) == 200 ms.
  • Bây giờ, bản thân delta trở thành gần 200 (vì bạn đã đo thời gian ngủ cũng như công việc thực tế) và bạn ngủ 200 - delta(200) == 0 ms.
  • Sau đó, chu kỳ lặp lại.

Để khắc phục sự cố bạn không cần đo thời gian ngủ.

Đây là cách nó có thể được thực hiện:

#include <iostream> 
#include <cstdio> 
#include <chrono> 
#include <thread> 

std::chrono::system_clock::time_point a = std::chrono::system_clock::now(); 
std::chrono::system_clock::time_point b = std::chrono::system_clock::now(); 

int main() 
{ 
    while (true) 
    { 
     // Maintain designated frequency of 5 Hz (200 ms per frame) 
     a = std::chrono::system_clock::now(); 
     std::chrono::duration<double, std::milli> work_time = a - b; 

     if (work_time.count() < 200.0) 
     { 
      std::chrono::duration<double, std::milli> delta_ms(200.0 - work_time.count()); 
      auto delta_ms_duration = std::chrono::duration_cast<std::chrono::milliseconds>(delta_ms); 
      std::this_thread::sleep_for(std::chrono::milliseconds(delta_ms_duration.count())); 
     } 

     b = std::chrono::system_clock::now(); 
     std::chrono::duration<double, std::milli> sleep_time = b - a; 

     // Your code here 

     printf("Time: %f \n", (work_time + sleep_time).count()); 
    } 
} 

Mã này mang lại cho tôi một chuỗi ổn định của đồng bằng châu thổ:

Time: 199.057206 
Time: 199.053581 
Time: 199.064718 
Time: 199.053515 
Time: 199.053307 
Time: 199.053415 
Time: 199.053164 
Time: 199.053511 
Time: 199.053280 
Time: 199.053283  
+0

Giải thích tuyệt vời, tôi thấy bây giờ tôi đã đi sai. Cảm ơn bạn. – Irongrave

+0

cuộc hội thoại bên trong sleep_fo() r không được yêu cầu, bằng chứng: static_assert (std :: is_same (), ""); không thất bại, –

4

Thời gian delta thay đổi phát sinh từ một vấn đề logic: bạn đang thêm trễ vào một khung dựa trên thời lượng của khung trước đó (về thời gian khung được tính). Điều này có nghĩa là sau một khung dài (~ 200ms) bạn không áp dụng độ trễ và nhận khung ngắn (vài ms), sau đó kích hoạt độ trễ trên khung tiếp theo tạo khung dài, v.v.

4

tôi thường là làm một cái gì đó như thế này:

#include <chrono> 
#include <iostream> 

int main() 
{ 
    using clock = std::chrono::steady_clock; 

    auto next_frame = clock::now(); 

    while(true) 
    { 
     next_frame += std::chrono::milliseconds(1000/5); // 5Hz 

     // do stuff 
     std::cout << std::time(0) << '\n'; // 5 for each second 

     // wait for end of frame 
     std::this_thread::sleep_until(next_frame); 
    } 
} 

Đầu ra: (năm cho mỗi giá trị thứ hai)

1470173964 
1470173964 
1470173964 
1470173964 
1470173964 
1470173965 
1470173965 
1470173965 
1470173965 
1470173965 
1470173966 
1470173966 
1470173966 
1470173966 
1470173966 
+3

Đây là yêu thích của tôi, ngoại trừ việc tôi sẽ xác định đơn vị 'khung' tùy chỉnh:' sử dụng khung = std :: chrono :: duration >; 'và sau đó làm 'next_from + = frames {1};'. Điều đó giúp chuyển đổi thủ công khỏi mã. –

7

Điều này giống như Galik's answer, nhưng nó giữ cú pháp của câu hỏi của OP và không thả xuống API C.Thêm vào đó nó tạo ra một đơn vị tùy chỉnh cho độ dài khung mà tôi tin là rất quan trọng để có thể đọc:

#include <chrono> 
#include <cstdint> 
#include <iostream> 
#include <thread> 

int 
main() 
{ 
    using namespace std; 
    using namespace std::chrono; 

    using frames = duration<int64_t, ratio<1, 5>>; // 5Hz 
    auto nextFrame = system_clock::now(); 
    auto lastFrame = nextFrame - frames{1};; 

    while (true) 
    { 
     // Perform intersection test 

     this_thread::sleep_until(nextFrame); 
     cout << "Time: " // just for monitoring purposes 
      << duration_cast<milliseconds>(system_clock::now() - lastFrame).count() 
      << "ms\n"; 
     lastFrame = nextFrame; 
     nextFrame += frames{1}; 
    } 
} 

này đầu ra cho tôi:

Time: 200ms 
Time: 205ms 
Time: 205ms 
Time: 203ms 
Time: 205ms 
Time: 205ms 
Time: 200ms 
Time: 200ms 
Time: 200ms 
... 

điều quan trọng cần lưu ý:

  • Một súc tích cách để tài liệu 5Hz: using frames = duration<int64_t, ratio<1, 5>>;
  • Sử dụng sleep_until thay vì sleep_for, công cụ này sẽ xử lý không biết phải mất bao lâu để hoàn thành công việc thực tế.
  • Không sử dụng .count() ngoại trừ I/O và here's a library to get rid of that.
  • Không chuyển đổi đơn vị đơn vị (ví dụ: / 1000).
  • Không có đơn vị điểm động nào, không có gì sai với điều đó.
  • Yêu cầu tối thiểu để chỉ định hoặc phụ thuộc vào đơn vị rõ ràng.

Với việc bổ sung các duration I/O library, đây là cách mã trên sẽ được thay đổi:

#include "chrono_io.h" 
#include <chrono> 
#include <cstdint> 
#include <iostream> 
#include <thread> 

int 
main() 
{ 
    using namespace date; 
    using namespace std; 
    using namespace std::chrono; 

    using frames = duration<int64_t, ratio<1, 5>>; // 5Hz 
    auto nextFrame = system_clock::now(); 
    auto lastFrame = nextFrame - frames{1};; 

    while (true) 
    { 
     // Perform intersection test 

     this_thread::sleep_until(nextFrame); 
     // just for monitoring purposes 
     cout << "Time: " << system_clock::now() - lastFrame << '\n'; 
     lastFrame = nextFrame; 
     nextFrame += frames{1}; 
    } 
} 

Sản lượng sẽ khác nhau tùy thuộc vào nền tảng (phụ thuộc vào "thời gian bản địa" của system_clock). Trên nền tảng của tôi, nó trông giống như sau:

Time: 200042µs 
Time: 205105µs 
Time: 205107µs 
Time: 200044µs 
Time: 205105µs 
Time: 200120µs 
Time: 204307µs 
Time: 205136µs 
Time: 201978µs 
... 
+0

Tôi nhận được lỗi biên dịch (cả với VS 2017 và GCC 7) khi tôi thay đổi tỷ lệ thành 30 ot 60 Hz - '' 'lỗi C2679: nhị phân '+ =': không tìm thấy toán tử nào có toán hạng bên phải của loại 'khung' (hoặc không có chuyển đổi chấp nhận được) '' '- bất kỳ ý tưởng nào tại sao? – onqtam

+1

Vâng, đây là trình biên dịch cảnh báo bạn rằng bạn có lỗi cắt ngắn khi thêm 1/30 giây (ví dụ) vào 'system_clock :: time_point' vì' system_clock :: time_point' không thể đại diện chính xác 1/30 của một giây. Nhưng điều này rất dễ sửa! Chỉ cần thêm 'frames {0}' vào system_clock :: now() và để 'auto' làm phép thuật của nó:' auto nextFrame = system_clock :: now() + frames {0}; '. Bây giờ thay vì quản lý trong 'system_clock :: time_point', bạn đang sử dụng một time_point có thể biểu diễn chính xác tổng của các khung và system_clock :: time_point precision. –

Các vấn đề liên quan